/*-------------------------------------------------------------------------
 *
 * utility.c
 *	  包含控制 POSTGRES 实用命令执行的函数。一度充当 Lisp 和 C 系统之间的接口。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/tcop/utility.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "access/reloptions.h"
#include "access/twophase.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_inherits.h"
#include "catalog/toasting.h"
#include "commands/alter.h"
#include "commands/async.h"
#include "commands/cluster.h"
#include "commands/collationcmds.h"
#include "commands/comment.h"
#include "commands/conversioncmds.h"
#include "commands/copy.h"
#include "commands/createas.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/discard.h"
#include "commands/event_trigger.h"
#include "commands/explain.h"
#include "commands/extension.h"
#include "commands/lockcmds.h"
#include "commands/matview.h"
#include "commands/policy.h"
#include "commands/portalcmds.h"
#include "commands/prepare.h"
#include "commands/proclang.h"
#include "commands/publicationcmds.h"
#include "commands/schemacmds.h"
#include "commands/seclabel.h"
#include "commands/sequence.h"
#include "commands/subscriptioncmds.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "commands/user.h"
#include "commands/vacuum.h"
#include "commands/view.h"
#include "miscadmin.h"
#include "parser/parse_utilcmd.h"
#include "postmaster/bgwriter.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteRemove.h"
#include "storage/fd.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"

/* 插件在ProcessUtility()中获取控制的挂钩 */
ProcessUtility_hook_type ProcessUtility_hook = NULL;

/* 本地函数声明 */
static int	fc_ClassifyUtilityCommandAsReadOnly(Node *fc_parsetree);
static void fc_ProcessUtilitySlow(ParseState *fc_pstate,
							   PlannedStmt *fc_pstmt,
							   const char *fc_queryString,
							   ProcessUtilityContext fc_context,
							   ParamListInfo fc_params,
							   QueryEnvironment *fc_queryEnv,
							   DestReceiver *fc_dest,
							   QueryCompletion *fc_qc);
static void fc_ExecDropStmt(DropStmt *fc_stmt, bool fc_isTopLevel);

/*
 * CommandIsReadOnly: 执行查询是否为只读？
 *
 * 这是比我们为XactReadOnly模式应用的测试严格得多；
 * 查询必须*确实*是只读的，因为调用者希望
 * 不为其执行CommandCounterIncrement。
 *
 * 注意：目前没有必要在这里支持原始或分析过的查询
 */
bool CommandIsReadOnly(PlannedStmt *fc_pstmt)
{
	Assert(IsA(fc_pstmt, PlannedStmt));
	switch (fc_pstmt->commandType)
	{
		case CMD_SELECT:
			if (fc_pstmt->rowMarks != NIL)
				return false;	/* SELECT FOR [KEY] UPDATE/SHARE */
			else if (fc_pstmt->hasModifyingCTE)
				return false;	/* 数据修改 CTE */
			else
				return true;
		case CMD_UPDATE:
		case CMD_INSERT:
		case CMD_DELETE:
		case CMD_MERGE:
			return false;
		case CMD_UTILITY:
			/* 暂时将所有实用命令视为读/写 */
			return false;
		default:
			elog(WARNING, "unrecognized commandType: %d",
				 (int) fc_pstmt->commandType);
			break;
	}
	return false;
}

/*
 * 确定实用命令的只读程度。
 *
 * 请注意src/include/utility/tcop.h中相关标志的定义。
 */
static int fc_ClassifyUtilityCommandAsReadOnly(Node *fc_parsetree)
{
	switch (nodeTag(fc_parsetree))
	{
		case T_AlterCollationStmt:
		case T_AlterDatabaseRefreshCollStmt:
		case T_AlterDatabaseSetStmt:
		case T_AlterDatabaseStmt:
		case T_AlterDefaultPrivilegesStmt:
		case T_AlterDomainStmt:
		case T_AlterEnumStmt:
		case T_AlterEventTrigStmt:
		case T_AlterExtensionContentsStmt:
		case T_AlterExtensionStmt:
		case T_AlterFdwStmt:
		case T_AlterForeignServerStmt:
		case T_AlterFunctionStmt:
		case T_AlterObjectDependsStmt:
		case T_AlterObjectSchemaStmt:
		case T_AlterOpFamilyStmt:
		case T_AlterOperatorStmt:
		case T_AlterOwnerStmt:
		case T_AlterPolicyStmt:
		case T_AlterPublicationStmt:
		case T_AlterRoleSetStmt:
		case T_AlterRoleStmt:
		case T_AlterSeqStmt:
		case T_AlterStatsStmt:
		case T_AlterSubscriptionStmt:
		case T_AlterTSConfigurationStmt:
		case T_AlterTSDictionaryStmt:
		case T_AlterTableMoveAllStmt:
		case T_AlterTableSpaceOptionsStmt:
		case T_AlterTableStmt:
		case T_AlterTypeStmt:
		case T_AlterUserMappingStmt:
		case T_CommentStmt:
		case T_CompositeTypeStmt:
		case T_CreateAmStmt:
		case T_CreateCastStmt:
		case T_CreateConversionStmt:
		case T_CreateDomainStmt:
		case T_CreateEnumStmt:
		case T_CreateEventTrigStmt:
		case T_CreateExtensionStmt:
		case T_CreateFdwStmt:
		case T_CreateForeignServerStmt:
		case T_CreateForeignTableStmt:
		case T_CreateFunctionStmt:
		case T_CreateOpClassStmt:
		case T_CreateOpFamilyStmt:
		case T_CreatePLangStmt:
		case T_CreatePolicyStmt:
		case T_CreatePublicationStmt:
		case T_CreateRangeStmt:
		case T_CreateRoleStmt:
		case T_CreateSchemaStmt:
		case T_CreateSeqStmt:
		case T_CreateStatsStmt:
		case T_CreateStmt:
		case T_CreateSubscriptionStmt:
		case T_CreateTableAsStmt:
		case T_CreateTableSpaceStmt:
		case T_CreateTransformStmt:
		case T_CreateTrigStmt:
		case T_CreateUserMappingStmt:
		case T_CreatedbStmt:
		case T_DefineStmt:
		case T_DropOwnedStmt:
		case T_DropRoleStmt:
		case T_DropStmt:
		case T_DropSubscriptionStmt:
		case T_DropTableSpaceStmt:
		case T_DropUserMappingStmt:
		case T_DropdbStmt:
		case T_GrantRoleStmt:
		case T_GrantStmt:
		case T_ImportForeignSchemaStmt:
		case T_IndexStmt:
		case T_ReassignOwnedStmt:
		case T_RefreshMatViewStmt:
		case T_RenameStmt:
		case T_RuleStmt:
		case T_SecLabelStmt:
		case T_TruncateStmt:
		case T_ViewStmt:
			{
				/* DDL 不是只读的，TRUNCATE 也不是。 */
				return COMMAND_IS_NOT_READ_ONLY;
			}

		case T_AlterSystemStmt:
			{
				/*
				 * 令人惊讶的是，ALTER SYSTEM 满足
				 * 我们对只读的所有定义：它不改变任何影响
				 * pg_dump 输出的内容，不写 WAL，也不会危及
				 * 未来 WAL 的应用，并且不依赖于需要
				 * 与并行工作者同步的任何状态。
				 *
				 * 所以，尽管它写入文件，但它是只读的！
				 */
				return COMMAND_IS_STRICTLY_READ_ONLY;
			}

		case T_CallStmt:
		case T_DoStmt:
			{
				/*
				 * DO块或被调用过程中的命令可能
				 * 不是只读的，但在我们尝试执行它们时
				 * 会单独检查。在这里我们只需关心
				 * DO 或 CALL 命令本身。
				 */
				return COMMAND_IS_STRICTLY_READ_ONLY;
			}

		case T_CheckPointStmt:
			{
				/*
				 * 你可能认为这在恢复中不应被允许，但我们将
				 * 恢复期间的 CHECKPOINT 命令解释为重启点的请求。
				 * 我们允许这样做，因为这可以在使用各种形式的复制时
				 * 减少切换时间。
				 */
				return COMMAND_IS_STRICTLY_READ_ONLY;
			}

		case T_ClosePortalStmt:
		case T_ConstraintsSetStmt:
		case T_DeallocateStmt:
		case T_DeclareCursorStmt:
		case T_DiscardStmt:
		case T_ExecuteStmt:
		case T_FetchStmt:
		case T_LoadStmt:
		case T_PrepareStmt:
		case T_UnlistenStmt:
		case T_VariableSetStmt:
			{
				/*
				 * 这些仅修改后端本地状态，因此它们可以在
				 * 只读事务或备机上运行。然而，它们在并行模式下
				 * 是不被允许的，因为它们依赖或修改可能在
				 * 合作后端之间不同步的后端本地状态。
				 */
				return COMMAND_OK_IN_RECOVERY | COMMAND_OK_IN_READ_ONLY_TXN;
			}

		case T_ClusterStmt:
		case T_ReindexStmt:
		case T_VacuumStmt:
			{
				/*
				 * 这些命令写入 WAL，因此它们不严格
				 * 是只读的，并且不支持在并行工作者中运行。
				 *
				 * 然而，它们不会通过影响pg_dump输出的方式
				 * 改变数据库状态，因此在只读事务中运行它们是可以的。
				 * (CLUSTER 可能会改变磁盘上行的顺序，这可能会影响
				 * pg_dump 输出的排序，但这在语义上并不重要。)
				 */
				return COMMAND_OK_IN_READ_ONLY_TXN;
			}

		case T_CopyStmt:
			{
				CopyStmt   *fc_stmt = (CopyStmt *) fc_parsetree;

				/*
				 * 你可能认为 COPY FROM 根本不是只读的，但
				 * 将数据复制到临时表是可以的，因为这不会改变
				 * pg_dump 的输出。如果目标表是非临时的，
				 * DoCopy本身将调用PreventCommandIfReadOnly。
				 */
				if (fc_stmt->is_from)
					return COMMAND_OK_IN_READ_ONLY_TXN;
				else
					return COMMAND_IS_STRICTLY_READ_ONLY;
			}

		case T_ExplainStmt:
		case T_VariableShowStmt:
			{
				/*
				 * 这些命令不修改任何数据，可以在
				 * 并行工作者中安全运行。
				 */
				return COMMAND_IS_STRICTLY_READ_ONLY;
			}

		case T_ListenStmt:
		case T_NotifyStmt:
			{
				/*
				 * NOTIFY 需要一个 XID 分配，因此不能在
				 * 备机上被允许。也许 LISTEN 可以，因为没有
				 * NOTIFY 的情况下什么都不做是可以的，至少在提升之前，
				 * 但我们目前禁止它，以免用户产生错误的想法。
				 *
				 * (不过我们确实允许在备机上执行 T_UnlistenStmt，
				 * 因为它是一个无操作。)
				 */
				return COMMAND_OK_IN_READ_ONLY_TXN;
			}

		case T_LockStmt:
			{
				LockStmt   *fc_stmt = (LockStmt *) fc_parsetree;

				/*
				 * 仅在恢复期间允许较弱的锁定模式。这里的
				 * 限制必须与 LockAcquireExtended() 中的限制一致。
				 */
				if (fc_stmt->mode > RowExclusiveLock)
					return COMMAND_OK_IN_READ_ONLY_TXN;
				else
					return COMMAND_IS_STRICTLY_READ_ONLY;
			}

		case T_TransactionStmt:
			{
				TransactionStmt *fc_stmt = (TransactionStmt *) fc_parsetree;

				/*
				 * PREPARE、COMMIT PREPARED 和 ROLLBACK PREPARED 都写入
				 * WAL，因此在严格意义上它们不是只读的；但
				 * 第一个和第三个不会改变 pg_dump 输出，因此在
				 * 只读事务中是可以的。
				 *
				 * 我们也认为 COMMIT PREPARED 在只读事务环境中是OK的，
				 * 作为例外。
				 */
				switch (fc_stmt->kind)
				{
					case TRANS_STMT_BEGIN:
					case TRANS_STMT_START:
					case TRANS_STMT_COMMIT:
					case TRANS_STMT_ROLLBACK:
					case TRANS_STMT_SAVEPOINT:
					case TRANS_STMT_RELEASE:
					case TRANS_STMT_ROLLBACK_TO:
						return COMMAND_IS_STRICTLY_READ_ONLY;

					case TRANS_STMT_PREPARE:
					case TRANS_STMT_COMMIT_PREPARED:
					case TRANS_STMT_ROLLBACK_PREPARED:
						return COMMAND_OK_IN_READ_ONLY_TXN;
				}
				elog(ERROR, "unrecognized TransactionStmtKind: %d",
					 (int) fc_stmt->kind);
				return 0;		/* 消除愚蠢编译器的警告 */
			}

		default:
			elog(ERROR, "unrecognized node type: %d",
				 (int) nodeTag(fc_parsetree));
			return 0;			/* 消除愚蠢编译器的警告 */
	}
}

/*
 * PreventCommandIfReadOnly: 如果XactReadOnly则抛出错误
 *
 * 这在一定程度上有助于确保错误消息措辞的一致性；
 * 某些调用者已经自行检查 XactReadOnly。
 */
void PreventCommandIfReadOnly(const char *fc_cmdname)
{
	if (XactReadOnly)
		ereport(ERROR,
				(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
		/* 翻译者：%s 是 SQL 命令的名称，例如 CREATE */
				 errmsg("cannot execute %s in a read-only transaction",
						fc_cmdname)));
}

/*
 * PreventCommandIfParallelMode: 如果当前（子）事务处于
 * 并行模式则抛出错误。
 *
 * 这在一定程度上有助于确保错误消息措辞的一致性；
 * 某些调用者已经自行检查 IsInParallelMode()。
 */
void PreventCommandIfParallelMode(const char *fc_cmdname)
{
	if (IsInParallelMode())
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
		/* 翻译者：%s 是 SQL 命令的名称，例如 CREATE */
				 errmsg("cannot execute %s during a parallel operation",
						fc_cmdname)));
}

/*
 * PreventCommandDuringRecovery: 如果正在恢复则抛出错误
 *
 * 在热备机中不安全的大多数操作将被 XactReadOnly 测试拒绝。
 * 然而，有一些命令在“只读”xacts 中是被允许的，但不能在热备模式下被允许。
 * 这些命令应该调用此函数。
 */
void PreventCommandDuringRecovery(const char *fc_cmdname)
{
	if (RecoveryInProgress())
		ereport(ERROR,
				(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
		/* 翻译者：%s 是 SQL 命令的名称，例如 CREATE */
				 errmsg("cannot execute %s during recovery",
						fc_cmdname)));
}


/*
 * CheckRestrictedOperation: 在安全限制上下文中，如果我们
 * 处于危险命令内，抛出错误。
 *
 * 这需要保护会话本地状态，对于这些状态没有
 * 更好定义的保护机制，例如所有权。
 */
static void fc_CheckRestrictedOperation(const char *fc_cmdname)
{
	if (InSecurityRestrictedOperation())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
		/* 翻译者：%s 是 SQL 命令的名称，例如 PREPARE */
				 errmsg("cannot execute %s within security-restricted operation",
						fc_cmdname)));
}

/*
 * ProcessUtility
 *		一般实用工具函数调用器
 *
 *	pstmt: 实用语句的 PlannedStmt 封装
 *	queryString: 命令的原始源文本
 *	readOnlyTree: 如果为真，pstmt 的节点树不能被修改
 *	context: 确定语句的来源（顶层客户端命令，
 *		非顶层客户端命令，更大的实用命令的子命令）
 *	params: 执行期间使用的参数
 *	queryEnv: 从解析到执行的环境（例如，短暂的命名
 *		表，例如触发器过渡表）。可以是 NULL。
 *	dest: 结果发送到哪里
 *	qc: 存储命令完成状态数据的地方。可以是 NULL，
 *		但如果不是，那么调用者必须已经初始化。
 *
 * 调用者必须提供 queryString；不允许（再也）传递 NULL。
 * 如果你真的没有源文本，你可以传递一个常量字符串，
 * 可能是“（查询不可用）”。
 *
 * 供 ProcessUtility_hook 的用户注意：同一个 queryString 可能
 * 会被传递给多次调用 ProcessUtility ，当处理一个包含多个以分号分隔的语句的查询字符串时。
 * 应该使用 pstmt->stmt_location 和 pstmt->stmt_len 来识别
 * 包含当前语句的子字符串。 还要记住，一些实用语句
 * （例如，创建架构）将递归调用 ProcessUtility 来处理
 * 子语句，通常传递相同的 queryString、stmt_location，
 * 和 stmt_len，这些是为整个语句给定的。
 */
void ProcessUtility(PlannedStmt *fc_pstmt,
			   const char *fc_queryString,
			   bool fc_readOnlyTree,
			   ProcessUtilityContext fc_context,
			   ParamListInfo fc_params,
			   QueryEnvironment *fc_queryEnv,
			   DestReceiver *fc_dest,
			   QueryCompletion *fc_qc)
{
	Assert(IsA(fc_pstmt, PlannedStmt));
	Assert(fc_pstmt->commandType == CMD_UTILITY);
	Assert(fc_queryString != NULL);	/* 从 8.4 开始要求 */
	Assert(fc_qc == NULL || fc_qc->commandTag == CMDTAG_UNKNOWN);

	/*
	 * 我们提供一个函数钩子变量，让可加载插件在调用 ProcessUtility 时获得
	 * 控制权。这样的插件通常会调用 standard_ProcessUtility()。
	 */
	if (ProcessUtility_hook)
		(*ProcessUtility_hook) (fc_pstmt, fc_queryString, fc_readOnlyTree,
								fc_context, fc_params, fc_queryEnv,
								fc_dest, fc_qc);
	else
		standard_ProcessUtility(fc_pstmt, fc_queryString, fc_readOnlyTree,
								fc_context, fc_params, fc_queryEnv,
								fc_dest, fc_qc);
}

/*
 * standard_ProcessUtility 自身仅处理没有事件触发支持的
 * 实用命令。具有此类支持的命令将传递给 ProcessUtilitySlow，
 * 它包含此类触发器所需的基础设施。
 *
 * 这种划分不仅仅是为了性能：在执行 START TRANSACTION 时，触发的
 * 事件代码不应被调用是至关重要的，因为我们可能需要刷新事件触发器
 * 缓存，这要求处于有效的事务中。
 */
void standard_ProcessUtility(PlannedStmt *fc_pstmt,
						const char *fc_queryString,
						bool fc_readOnlyTree,
						ProcessUtilityContext fc_context,
						ParamListInfo fc_params,
						QueryEnvironment *fc_queryEnv,
						DestReceiver *fc_dest,
						QueryCompletion *fc_qc)
{
	Node	   *fc_parsetree;
	bool		fc_isTopLevel = (fc_context == PROCESS_UTILITY_TOPLEVEL);
	bool		fc_isAtomicContext = (!(fc_context == PROCESS_UTILITY_TOPLEVEL || fc_context == PROCESS_UTILITY_QUERY_NONATOMIC) || IsTransactionBlock());
	ParseState *fc_pstate;
	int			fc_readonly_flags;

	/* 这可能会递归，因此检查过度递归 */
	check_stack_depth();

	/*
	 * 如果给定的节点树是只读的，进行复制以确保解析
	 * 转换不会损害原始树。这可能被重构以避免在更多情况下
	 * 进行不必要的复制，但不清楚这是否值得付出很多努力。
	 * 复杂到足以昂贵的复制的语句正是我们需要复制的语句，
	 * 因此只有边际节省似乎是可能的。
	 */
	if (fc_readOnlyTree)
		fc_pstmt = copyObject(fc_pstmt);
	fc_parsetree = fc_pstmt->utilityStmt;

	/* 禁止在只读状态下执行读/写命令。 */
	fc_readonly_flags = fc_ClassifyUtilityCommandAsReadOnly(fc_parsetree);
	if (fc_readonly_flags != COMMAND_IS_STRICTLY_READ_ONLY &&
		(XactReadOnly || IsInParallelMode()))
	{
		CommandTag	fc_commandtag = CreateCommandTag(fc_parsetree);

		if ((fc_readonly_flags & COMMAND_OK_IN_READ_ONLY_TXN) == 0)
			PreventCommandIfReadOnly(GetCommandTagName(fc_commandtag));
		if ((fc_readonly_flags & COMMAND_OK_IN_PARALLEL_MODE) == 0)
			PreventCommandIfParallelMode(GetCommandTagName(fc_commandtag));
		if ((fc_readonly_flags & COMMAND_OK_IN_RECOVERY) == 0)
			PreventCommandDuringRecovery(GetCommandTagName(fc_commandtag));
	}

	fc_pstate = make_parsestate(NULL);
	fc_pstate->p_sourcetext = fc_queryString;
	fc_pstate->p_queryEnv = fc_queryEnv;

	switch (nodeTag(fc_parsetree))
	{
			/*
			 * ******************** 事务 ********************
			 */
		case T_TransactionStmt:
			{
				TransactionStmt *fc_stmt = (TransactionStmt *) fc_parsetree;

				switch (fc_stmt->kind)
				{
						/*
						 * 启动事务，如SQL99所定义：与BEGIN相同。
						 * 两者使用相同的代码。
						 */
					case TRANS_STMT_BEGIN:
					case TRANS_STMT_START:
						{
							ListCell   *fc_lc;

							BeginTransactionBlock();
							foreach(fc_lc, fc_stmt->options)
							{
								DefElem    *fc_item = (DefElem *) lfirst(fc_lc);

								if (strcmp(fc_item->defname, "transaction_isolation") == 0)
									SetPGVariable("transaction_isolation",
												  list_make1(fc_item->arg),
												  true);
								else if (strcmp(fc_item->defname, "transaction_read_only") == 0)
									SetPGVariable("transaction_read_only",
												  list_make1(fc_item->arg),
												  true);
								else if (strcmp(fc_item->defname, "transaction_deferrable") == 0)
									SetPGVariable("transaction_deferrable",
												  list_make1(fc_item->arg),
												  true);
							}
						}
						break;

					case TRANS_STMT_COMMIT:
						if (!EndTransactionBlock(fc_stmt->chain))
						{
							/* 在qc中报告未成功的提交 */
							if (fc_qc)
								SetQueryCompletion(fc_qc, CMDTAG_ROLLBACK, 0);
						}
						break;

					case TRANS_STMT_PREPARE:
						if (!PrepareTransactionBlock(fc_stmt->gid))
						{
							/* 在qc中报告未成功的提交 */
							if (fc_qc)
								SetQueryCompletion(fc_qc, CMDTAG_ROLLBACK, 0);
						}
						break;

					case TRANS_STMT_COMMIT_PREPARED:
						PreventInTransactionBlock(fc_isTopLevel, "COMMIT PREPARED");
						FinishPreparedTransaction(fc_stmt->gid, true);
						break;

					case TRANS_STMT_ROLLBACK_PREPARED:
						PreventInTransactionBlock(fc_isTopLevel, "ROLLBACK PREPARED");
						FinishPreparedTransaction(fc_stmt->gid, false);
						break;

					case TRANS_STMT_ROLLBACK:
						UserAbortTransactionBlock(fc_stmt->chain);
						break;

					case TRANS_STMT_SAVEPOINT:
						RequireTransactionBlock(fc_isTopLevel, "SAVEPOINT");
						DefineSavepoint(fc_stmt->savepoint_name);
						break;

					case TRANS_STMT_RELEASE:
						RequireTransactionBlock(fc_isTopLevel, "RELEASE SAVEPOINT");
						ReleaseSavepoint(fc_stmt->savepoint_name);
						break;

					case TRANS_STMT_ROLLBACK_TO:
						RequireTransactionBlock(fc_isTopLevel, "ROLLBACK TO SAVEPOINT");
						RollbackToSavepoint(fc_stmt->savepoint_name);

						/*
						 * CommitTransactionCommand负责
						 * 重新定义保存点
						 */
						break;
				}
			}
			break;

			/*
			 * Portal（游标）操作
			 */
		case T_DeclareCursorStmt:
			PerformCursorOpen(fc_pstate, (DeclareCursorStmt *) fc_parsetree, fc_params,
							  fc_isTopLevel);
			break;

		case T_ClosePortalStmt:
			{
				ClosePortalStmt *fc_stmt = (ClosePortalStmt *) fc_parsetree;

				fc_CheckRestrictedOperation("CLOSE");
				PerformPortalClose(fc_stmt->portalname);
			}
			break;

		case T_FetchStmt:
			PerformPortalFetch((FetchStmt *) fc_parsetree, fc_dest, fc_qc);
			break;

		case T_DoStmt:
			ExecuteDoStmt(fc_pstate, (DoStmt *) fc_parsetree, fc_isAtomicContext);
			break;

		case T_CreateTableSpaceStmt:
			/* 全局对象没有事件触发器 */
			PreventInTransactionBlock(fc_isTopLevel, "CREATE TABLESPACE");
			CreateTableSpace((CreateTableSpaceStmt *) fc_parsetree);
			break;

		case T_DropTableSpaceStmt:
			/* 全局对象没有事件触发器 */
			PreventInTransactionBlock(fc_isTopLevel, "DROP TABLESPACE");
			DropTableSpace((DropTableSpaceStmt *) fc_parsetree);
			break;

		case T_AlterTableSpaceOptionsStmt:
			/* 全局对象没有事件触发器 */
			AlterTableSpaceOptions((AlterTableSpaceOptionsStmt *) fc_parsetree);
			break;

		case T_TruncateStmt:
			ExecuteTruncate((TruncateStmt *) fc_parsetree);
			break;

		case T_CopyStmt:
			{
				uint64		fc_processed;

				DoCopy(fc_pstate, (CopyStmt *) fc_parsetree,
					   fc_pstmt->stmt_location, fc_pstmt->stmt_len,
					   &fc_processed);
				if (fc_qc)
					SetQueryCompletion(fc_qc, CMDTAG_COPY, fc_processed);
			}
			break;

		case T_PrepareStmt:
			fc_CheckRestrictedOperation("PREPARE");
			PrepareQuery(fc_pstate, (PrepareStmt *) fc_parsetree,
						 fc_pstmt->stmt_location, fc_pstmt->stmt_len);
			break;

		case T_ExecuteStmt:
			ExecuteQuery(fc_pstate,
						 (ExecuteStmt *) fc_parsetree, NULL,
						 fc_params,
						 fc_dest, fc_qc);
			break;

		case T_DeallocateStmt:
			fc_CheckRestrictedOperation("DEALLOCATE");
			DeallocateQuery((DeallocateStmt *) fc_parsetree);
			break;

		case T_GrantRoleStmt:
			/* 全局对象没有事件触发器 */
			GrantRole((GrantRoleStmt *) fc_parsetree);
			break;

		case T_CreatedbStmt:
			/* 全局对象没有事件触发器 */
			PreventInTransactionBlock(fc_isTopLevel, "CREATE DATABASE");
			createdb(fc_pstate, (CreatedbStmt *) fc_parsetree);
			break;

		case T_AlterDatabaseStmt:
			/* 全局对象没有事件触发器 */
			AlterDatabase(fc_pstate, (AlterDatabaseStmt *) fc_parsetree, fc_isTopLevel);
			break;

		case T_AlterDatabaseRefreshCollStmt:
			/* 全局对象没有事件触发器 */
			AlterDatabaseRefreshColl((AlterDatabaseRefreshCollStmt *) fc_parsetree);
			break;

		case T_AlterDatabaseSetStmt:
			/* 全局对象没有事件触发器 */
			AlterDatabaseSet((AlterDatabaseSetStmt *) fc_parsetree);
			break;

		case T_DropdbStmt:
			/* 全局对象没有事件触发器 */
			PreventInTransactionBlock(fc_isTopLevel, "DROP DATABASE");
			DropDatabase(fc_pstate, (DropdbStmt *) fc_parsetree);
			break;

			/* 查询级别的异步通知 */
		case T_NotifyStmt:
			{
				NotifyStmt *fc_stmt = (NotifyStmt *) fc_parsetree;

				Async_Notify(fc_stmt->conditionname, fc_stmt->payload);
			}
			break;

		case T_ListenStmt:
			{
				ListenStmt *fc_stmt = (ListenStmt *) fc_parsetree;

				fc_CheckRestrictedOperation("LISTEN");

				/*
				 * 我们不允许在后台进程中使用LISTEN，因为没有机制
				 * 收集NOTIFY消息，因此它们只会无限期阻塞异步SLRU的清理。
				 * （自定义后台工作者的作者可以通过直接调用
				 * Async_Listen来绕过此限制，但那样就得由他们提供某种
				 * 机制来处理消息队列。）请注意，似乎没有理由禁止UNLISTEN。
				 */
				if (MyBackendType != B_BACKEND)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					/* 翻译者：%s是SQL命令的名称，例如LISTEN */
							 errmsg("cannot execute %s within a background process",
									"LISTEN")));

				Async_Listen(fc_stmt->conditionname);
			}
			break;

		case T_UnlistenStmt:
			{
				UnlistenStmt *fc_stmt = (UnlistenStmt *) fc_parsetree;

				fc_CheckRestrictedOperation("UNLISTEN");
				if (fc_stmt->conditionname)
					Async_Unlisten(fc_stmt->conditionname);
				else
					Async_UnlistenAll();
			}
			break;

		case T_LoadStmt:
			{
				LoadStmt   *fc_stmt = (LoadStmt *) fc_parsetree;

				closeAllVfds(); /* 可能没有必要…… */
				/* 如果您不是超级用户，则允许的名称受到限制 */
				load_file(fc_stmt->filename, !superuser());
			}
			break;

		case T_CallStmt:
			ExecuteCallStmt(castNode(CallStmt, fc_parsetree), fc_params, fc_isAtomicContext, fc_dest);
			break;

		case T_ClusterStmt:
			cluster(fc_pstate, (ClusterStmt *) fc_parsetree, fc_isTopLevel);
			break;

		case T_VacuumStmt:
			ExecVacuum(fc_pstate, (VacuumStmt *) fc_parsetree, fc_isTopLevel);
			break;

		case T_ExplainStmt:
			ExplainQuery(fc_pstate, (ExplainStmt *) fc_parsetree, fc_params, fc_dest);
			break;

		case T_AlterSystemStmt:
			PreventInTransactionBlock(fc_isTopLevel, "ALTER SYSTEM");
			AlterSystemSetConfigFile((AlterSystemStmt *) fc_parsetree);
			break;

		case T_VariableSetStmt:
			ExecSetVariableStmt((VariableSetStmt *) fc_parsetree, fc_isTopLevel);
			break;

		case T_VariableShowStmt:
			{
				VariableShowStmt *fc_n = (VariableShowStmt *) fc_parsetree;

				GetPGVariable(fc_n->name, fc_dest);
			}
			break;

		case T_DiscardStmt:
			/* 我们应该允许DISCARD PLANS吗？ */
			fc_CheckRestrictedOperation("DISCARD");
			DiscardCommand((DiscardStmt *) fc_parsetree, fc_isTopLevel);
			break;

		case T_CreateEventTrigStmt:
			/* 在事件触发器上没有事件触发器 */
			CreateEventTrigger((CreateEventTrigStmt *) fc_parsetree);
			break;

		case T_AlterEventTrigStmt:
			/* 在事件触发器上没有事件触发器 */
			AlterEventTrigger((AlterEventTrigStmt *) fc_parsetree);
			break;

			/*
			 * ******************************** 角色语句 ****
			 */
		case T_CreateRoleStmt:
			/* 全局对象没有事件触发器 */
			CreateRole(fc_pstate, (CreateRoleStmt *) fc_parsetree);
			break;

		case T_AlterRoleStmt:
			/* 全局对象没有事件触发器 */
			AlterRole(fc_pstate, (AlterRoleStmt *) fc_parsetree);
			break;

		case T_AlterRoleSetStmt:
			/* 全局对象没有事件触发器 */
			AlterRoleSet((AlterRoleSetStmt *) fc_parsetree);
			break;

		case T_DropRoleStmt:
			/* 全局对象没有事件触发器 */
			DropRole((DropRoleStmt *) fc_parsetree);
			break;

		case T_ReassignOwnedStmt:
			/* 全局对象没有事件触发器 */
			ReassignOwnedObjects((ReassignOwnedStmt *) fc_parsetree);
			break;

		case T_LockStmt:

			/*
			 * 由于锁会立即被释放，因此在事务块外的LOCK TABLE
			 * 被认为是用户错误。
			 */
			RequireTransactionBlock(fc_isTopLevel, "LOCK TABLE");
			LockTableCommand((LockStmt *) fc_parsetree);
			break;

		case T_ConstraintsSetStmt:
			WarnNoTransactionBlock(fc_isTopLevel, "SET CONSTRAINTS");
			AfterTriggerSetState((ConstraintsSetStmt *) fc_parsetree);
			break;

		case T_CheckPointStmt:
			if (!has_privs_of_role(GetUserId(), ROLE_PG_CHECKPOINT))
				ereport(ERROR,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("must be superuser or have privileges of pg_checkpoint to do CHECKPOINT")));

			RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
							  (RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
			break;

		case T_ReindexStmt:
			ExecReindex(fc_pstate, (ReindexStmt *) fc_parsetree, fc_isTopLevel);
			break;

			/*
			 * 以下语句仅在某些情况下由事件触发器支持，因此
			 * 我们在其他情况下“快速路径”它们。
			 */

		case T_GrantStmt:
			{
				GrantStmt  *fc_stmt = (GrantStmt *) fc_parsetree;

				if (EventTriggerSupportsObjectType(fc_stmt->objtype))
					fc_ProcessUtilitySlow(fc_pstate, fc_pstmt, fc_queryString,
									   fc_context, fc_params, fc_queryEnv,
									   fc_dest, fc_qc);
				else
					ExecuteGrantStmt(fc_stmt);
			}
			break;

		case T_DropStmt:
			{
				DropStmt   *fc_stmt = (DropStmt *) fc_parsetree;

				if (EventTriggerSupportsObjectType(fc_stmt->removeType))
					fc_ProcessUtilitySlow(fc_pstate, fc_pstmt, fc_queryString,
									   fc_context, fc_params, fc_queryEnv,
									   fc_dest, fc_qc);
				else
					fc_ExecDropStmt(fc_stmt, fc_isTopLevel);
			}
			break;

		case T_RenameStmt:
			{
				RenameStmt *fc_stmt = (RenameStmt *) fc_parsetree;

				if (EventTriggerSupportsObjectType(fc_stmt->renameType))
					fc_ProcessUtilitySlow(fc_pstate, fc_pstmt, fc_queryString,
									   fc_context, fc_params, fc_queryEnv,
									   fc_dest, fc_qc);
				else
					ExecRenameStmt(fc_stmt);
			}
			break;

		case T_AlterObjectDependsStmt:
			{
				AlterObjectDependsStmt *fc_stmt = (AlterObjectDependsStmt *) fc_parsetree;

				if (EventTriggerSupportsObjectType(fc_stmt->objectType))
					fc_ProcessUtilitySlow(fc_pstate, fc_pstmt, fc_queryString,
									   fc_context, fc_params, fc_queryEnv,
									   fc_dest, fc_qc);
				else
					ExecAlterObjectDependsStmt(fc_stmt, NULL);
			}
			break;

		case T_AlterObjectSchemaStmt:
			{
				AlterObjectSchemaStmt *fc_stmt = (AlterObjectSchemaStmt *) fc_parsetree;

				if (EventTriggerSupportsObjectType(fc_stmt->objectType))
					fc_ProcessUtilitySlow(fc_pstate, fc_pstmt, fc_queryString,
									   fc_context, fc_params, fc_queryEnv,
									   fc_dest, fc_qc);
				else
					ExecAlterObjectSchemaStmt(fc_stmt, NULL);
			}
			break;

		case T_AlterOwnerStmt:
			{
				AlterOwnerStmt *fc_stmt = (AlterOwnerStmt *) fc_parsetree;

				if (EventTriggerSupportsObjectType(fc_stmt->objectType))
					fc_ProcessUtilitySlow(fc_pstate, fc_pstmt, fc_queryString,
									   fc_context, fc_params, fc_queryEnv,
									   fc_dest, fc_qc);
				else
					ExecAlterOwnerStmt(fc_stmt);
			}
			break;

		case T_CommentStmt:
			{
				CommentStmt *fc_stmt = (CommentStmt *) fc_parsetree;

				if (EventTriggerSupportsObjectType(fc_stmt->objtype))
					fc_ProcessUtilitySlow(fc_pstate, fc_pstmt, fc_queryString,
									   fc_context, fc_params, fc_queryEnv,
									   fc_dest, fc_qc);
				else
					CommentObject(fc_stmt);
				break;
			}

		case T_SecLabelStmt:
			{
				SecLabelStmt *fc_stmt = (SecLabelStmt *) fc_parsetree;

				if (EventTriggerSupportsObjectType(fc_stmt->objtype))
					fc_ProcessUtilitySlow(fc_pstate, fc_pstmt, fc_queryString,
									   fc_context, fc_params, fc_queryEnv,
									   fc_dest, fc_qc);
				else
					ExecSecLabelStmt(fc_stmt);
				break;
			}

		default:
			/* 所有其他语句类型都支持事件触发器 */
			fc_ProcessUtilitySlow(fc_pstate, fc_pstmt, fc_queryString,
							   fc_context, fc_params, fc_queryEnv,
							   fc_dest, fc_qc);
			break;
	}

	free_parsestate(fc_pstate);

	/*
	 * 使命令的效果可见，例如，以便
	 * PreCommit_on_commit_actions()可以看到它们（例如，
	 * 参见错误#15631）。
	 */
	CommandCounterIncrement();
}

/*
 * ProcessUtility的“慢”变体应仅接收由事件触发器
 * 功能支持的语句。因此，只要上下文允许，
 * 我们始终执行触发器支持调用。
 */
static void fc_ProcessUtilitySlow(ParseState *fc_pstate,
				   PlannedStmt *fc_pstmt,
				   const char *fc_queryString,
				   ProcessUtilityContext fc_context,
				   ParamListInfo fc_params,
				   QueryEnvironment *fc_queryEnv,
				   DestReceiver *fc_dest,
				   QueryCompletion *fc_qc)
{
	Node	   *fc_parsetree = fc_pstmt->utilityStmt;
	bool		fc_isTopLevel = (fc_context == PROCESS_UTILITY_TOPLEVEL);
	bool		fc_isCompleteQuery = (fc_context != PROCESS_UTILITY_SUBCOMMAND);
	bool		fc_needCleanup;
	bool		fc_commandCollected = false;
	ObjectAddress fc_address;
	ObjectAddress fc_secondaryObject = InvalidObjectAddress;

	/* 仅在isCompleteQuery为true时才执行所有事件触发器调用 */
	fc_needCleanup = fc_isCompleteQuery && EventTriggerBeginCompleteQuery();

	/* PG_TRY块用于确保我们调用EventTriggerEndCompleteQuery */
	PG_TRY();
	{
		if (fc_isCompleteQuery)
			EventTriggerDDLCommandStart(fc_parsetree);

		switch (nodeTag(fc_parsetree))
		{
				/*
				 * 关系和属性操作
				 */
			case T_CreateSchemaStmt:
				CreateSchemaCommand((CreateSchemaStmt *) fc_parsetree,
									fc_queryString,
									fc_pstmt->stmt_location,
									fc_pstmt->stmt_len);

				/*
				 * EventTriggerCollectSimpleCommand由
				 * CreateSchemaCommand调用
				 */
				fc_commandCollected = true;
				break;

			case T_CreateStmt:
			case T_CreateForeignTableStmt:
				{
					List	   *fc_stmts;
					RangeVar   *fc_table_rv = NULL;

					/* 运行解析分析... */
					fc_stmts = transformCreateStmt((CreateStmt *) fc_parsetree,
												fc_queryString);

					/*
					 * ...并执行它。我们不能使用foreach()，因为
					 * 我们可能在中途修改列表，因此逐个取出
					 * 元素，费力地处理。
					 */
					while (fc_stmts != NIL)
					{
						Node	   *fc_stmt = (Node *) linitial(fc_stmts);

						fc_stmts = list_delete_first(fc_stmts);

						if (IsA(fc_stmt, CreateStmt))
						{
							CreateStmt *fc_cstmt = (CreateStmt *) fc_stmt;
							Datum		fc_toast_options;
							static char *fc_validnsps[] = HEAP_RELOPT_NAMESPACES;

							/* 记住归一化的RangeVar以便用于LIKE */
							fc_table_rv = fc_cstmt->relation;

							/* 创建表本身 */
							fc_address = DefineRelation(fc_cstmt,
													 RELKIND_RELATION,
													 InvalidOid, NULL,
													 fc_queryString);
							EventTriggerCollectSimpleCommand(fc_address,
															 fc_secondaryObject,
															 fc_stmt);

							/*
							 * 让NewRelationCreateToastTable决定
							 * 这一个是否需要一个附属关系。
							 */
							CommandCounterIncrement();

							/*
							 * 解析并验证toast
							 * 表的关系选项
							 */
							fc_toast_options = transformRelOptions((Datum) 0,
																fc_cstmt->options,
																"toast",
																fc_validnsps,
																true,
																false);
							(void) heap_reloptions(RELKIND_TOASTVALUE,
												   fc_toast_options,
												   true);

							NewRelationCreateToastTable(fc_address.objectId,
														fc_toast_options);
						}
						else if (IsA(fc_stmt, CreateForeignTableStmt))
						{
							CreateForeignTableStmt *fc_cstmt = (CreateForeignTableStmt *) fc_stmt;

							/* 记住归一化的RangeVar以便用于LIKE */
							fc_table_rv = fc_cstmt->base.relation;

							/* 创建表本身 */
							fc_address = DefineRelation(&fc_cstmt->base,
													 RELKIND_FOREIGN_TABLE,
													 InvalidOid, NULL,
													 fc_queryString);
							CreateForeignTable(fc_cstmt,
											   fc_address.objectId);
							EventTriggerCollectSimpleCommand(fc_address,
															 fc_secondaryObject,
															 fc_stmt);
						}
						else if (IsA(fc_stmt, TableLikeClause))
						{
							/*
							 * 延迟处理 LIKE 选项。这
							 * 将导致我们处理额外的子语句。
							 * 这些应该在任何剩余的操作之前完成，因此将它们添加到“stmts”前面。
							 */
							TableLikeClause *fc_like = (TableLikeClause *) fc_stmt;
							List	   *fc_morestmts;

							Assert(fc_table_rv != NULL);

							fc_morestmts = expandTableLikeClause(fc_table_rv, fc_like);
							fc_stmts = list_concat(fc_morestmts, fc_stmts);
						}
						else
						{
							/*
							 * 递归处理其他事务。注意递归
							 * 调用将创建的对象存储到我们的
							 * 事件触发上下文中。
							 */
							PlannedStmt *fc_wrapper;

							fc_wrapper = makeNode(PlannedStmt);
							fc_wrapper->commandType = CMD_UTILITY;
							fc_wrapper->canSetTag = false;
							fc_wrapper->utilityStmt = fc_stmt;
							fc_wrapper->stmt_location = fc_pstmt->stmt_location;
							fc_wrapper->stmt_len = fc_pstmt->stmt_len;

							ProcessUtility(fc_wrapper,
										   fc_queryString,
										   false,
										   PROCESS_UTILITY_SUBCOMMAND,
										   fc_params,
										   NULL,
										   None_Receiver,
										   NULL);
						}

						/* 需要在命令之间进行 CCI */
						if (fc_stmts != NIL)
							CommandCounterIncrement();
					}

					/*
					 * 这里生成的多个命令是单独存储的，
					 * 所以在下面禁用收集。
					 */
					fc_commandCollected = true;
				}
				break;

			case T_AlterTableStmt:
				{
					AlterTableStmt *fc_atstmt = (AlterTableStmt *) fc_parsetree;
					Oid			fc_relid;
					LOCKMODE	fc_lockmode;
					ListCell   *fc_cell;

					/*
					 * 在事务块或函数中不允许使用 ALTER TABLE .. DETACH CONCURRENTLY。
					 * （也许可以在过程内允许，但别抱太大希望。）
					 */
					foreach(fc_cell, fc_atstmt->cmds)
					{
						AlterTableCmd *fc_cmd = (AlterTableCmd *) lfirst(fc_cell);

						/* 在事务块中不允许使用 DETACH CONCURRENTLY */
						if (fc_cmd->subtype == AT_DetachPartition)
						{
							if (((PartitionCmd *) fc_cmd->def)->concurrent)
								PreventInTransactionBlock(fc_isTopLevel,
														  "ALTER TABLE ... DETACH CONCURRENTLY");
						}
					}

					/*
					 * 确定锁模式并获取锁。这也会
					 * 进行基本权限检查，以免我们在
					 * 没有权限的关系上等待锁（例如）。
					 */
					fc_lockmode = AlterTableGetLockLevel(fc_atstmt->cmds);
					fc_relid = AlterTableLookupRelation(fc_atstmt, fc_lockmode);

					if (OidIsValid(fc_relid))
					{
						AlterTableUtilityContext fc_atcontext;

						/* 设置递归回调所需的上下文信息... */
						fc_atcontext.pstmt = fc_pstmt;
						fc_atcontext.queryString = fc_queryString;
						fc_atcontext.relid = fc_relid;
						fc_atcontext.params = fc_params;
						fc_atcontext.queryEnv = fc_queryEnv;

						/* ... 确保我们有事件触发上下文... */
						EventTriggerAlterTableStart(fc_parsetree);
						EventTriggerAlterTableRelid(fc_relid);

						/* ... 并执行此操作 */
						AlterTable(fc_atstmt, fc_lockmode, &fc_atcontext);

						/* 完成 */
						EventTriggerAlterTableEnd();
					}
					else
						ereport(NOTICE,
								(errmsg("relation \"%s\" does not exist, skipping",
										fc_atstmt->relation->relname)));
				}

				/* ALTER TABLE 内部存储命令 */
				fc_commandCollected = true;
				break;

			case T_AlterDomainStmt:
				{
					AlterDomainStmt *fc_stmt = (AlterDomainStmt *) fc_parsetree;

					/*
					 * 这些函数中的一些或全部是递归的，以涵盖
					 * 继承的内容，因此在那里进行了权限检查。
					 */
					switch (fc_stmt->subtype)
					{
						case 'T':	/* ALTER DOMAIN DEFAULT */

							/*
							 * 递归地更改表的列默认值，并且
							 * 如果要求的话，对后代进行更改
							 */
							fc_address =
								AlterDomainDefault(fc_stmt->typeName,
												   fc_stmt->def);
							break;
						case 'N':	/* ALTER DOMAIN DROP NOT NULL */
							fc_address =
								AlterDomainNotNull(fc_stmt->typeName,
												   false);
							break;
						case 'O':	/* ALTER DOMAIN SET NOT NULL */
							fc_address =
								AlterDomainNotNull(fc_stmt->typeName,
												   true);
							break;
						case 'C':	/* ADD CONSTRAINT */
							fc_address =
								AlterDomainAddConstraint(fc_stmt->typeName,
														 fc_stmt->def,
														 &fc_secondaryObject);
							break;
						case 'X':	/* DROP CONSTRAINT */
							fc_address =
								AlterDomainDropConstraint(fc_stmt->typeName,
														  fc_stmt->name,
														  fc_stmt->behavior,
														  fc_stmt->missing_ok);
							break;
						case 'V':	/* VALIDATE CONSTRAINT */
							fc_address =
								AlterDomainValidateConstraint(fc_stmt->typeName,
															  fc_stmt->name);
							break;
						default:	/* 哦，糟糕 */
							elog(ERROR, "unrecognized alter domain type: %d",
								 (int) fc_stmt->subtype);
							break;
					}
				}
				break;

				/*
				 * ************* 对象创建 / 销毁 **************
				 */
			case T_DefineStmt:
				{
					DefineStmt *fc_stmt = (DefineStmt *) fc_parsetree;

					switch (fc_stmt->kind)
					{
						case OBJECT_AGGREGATE:
							fc_address =
								DefineAggregate(fc_pstate, fc_stmt->defnames, fc_stmt->args,
												fc_stmt->oldstyle,
												fc_stmt->definition,
												fc_stmt->replace);
							break;
						case OBJECT_OPERATOR:
							Assert(fc_stmt->args == NIL);
							fc_address = DefineOperator(fc_stmt->defnames,
													 fc_stmt->definition);
							break;
						case OBJECT_TYPE:
							Assert(fc_stmt->args == NIL);
							fc_address = DefineType(fc_pstate,
												 fc_stmt->defnames,
												 fc_stmt->definition);
							break;
						case OBJECT_TSPARSER:
							Assert(fc_stmt->args == NIL);
							fc_address = DefineTSParser(fc_stmt->defnames,
													 fc_stmt->definition);
							break;
						case OBJECT_TSDICTIONARY:
							Assert(fc_stmt->args == NIL);
							fc_address = DefineTSDictionary(fc_stmt->defnames,
														 fc_stmt->definition);
							break;
						case OBJECT_TSTEMPLATE:
							Assert(fc_stmt->args == NIL);
							fc_address = DefineTSTemplate(fc_stmt->defnames,
													   fc_stmt->definition);
							break;
						case OBJECT_TSCONFIGURATION:
							Assert(fc_stmt->args == NIL);
							fc_address = DefineTSConfiguration(fc_stmt->defnames,
															fc_stmt->definition,
															&fc_secondaryObject);
							break;
						case OBJECT_COLLATION:
							Assert(fc_stmt->args == NIL);
							fc_address = DefineCollation(fc_pstate,
													  fc_stmt->defnames,
													  fc_stmt->definition,
													  fc_stmt->if_not_exists);
							break;
						default:
							elog(ERROR, "unrecognized define stmt type: %d",
								 (int) fc_stmt->kind);
							break;
					}
				}
				break;

			case T_IndexStmt:	/* CREATE INDEX */
				{
					IndexStmt  *fc_stmt = (IndexStmt *) fc_parsetree;
					Oid			fc_relid;
					LOCKMODE	fc_lockmode;
					bool		fc_is_alter_table;

					if (fc_stmt->concurrent)
						PreventInTransactionBlock(fc_isTopLevel,
												  "CREATE INDEX CONCURRENTLY");

					/*
					 * 只需在这里的开头查找关系 OID 一次，
					 * 以便我们不会在稍后重复名称
					 * 查找并意外链接到不同的关系。
					 * 为了避免锁升级的风险，
					 * 重要的是在这里采取最终需要的最强锁，
					 * 因此锁模式计算需要与 DefineIndex() 
					 * 的计算相匹配。
					 */
					fc_lockmode = fc_stmt->concurrent ? ShareUpdateExclusiveLock
						: ShareLock;
					fc_relid =
						RangeVarGetRelidExtended(fc_stmt->relation, fc_lockmode,
												 0,
												 RangeVarCallbackOwnsRelation,
												 NULL);

					/*
					 * 在分区表上 CREATE INDEX（而不是常规
					 * 继承表）会递归到分区，因此我们必须
					 * 早期获取锁以避免死锁。
					 *
					 * 我们还借此机会验证所有
					 * 分区都是我们可以创建索引的，
					 * 以避免仅在后期失败。
					 */
					if (fc_stmt->relation->inh &&
						get_rel_relkind(fc_relid) == RELKIND_PARTITIONED_TABLE)
					{
						ListCell   *fc_lc;
						List	   *fc_inheritors = NIL;

						fc_inheritors = find_all_inheritors(fc_relid, fc_lockmode, NULL);
						foreach(fc_lc, fc_inheritors)
						{
							char		fc_relkind = get_rel_relkind(lfirst_oid(fc_lc));

							if (fc_relkind != RELKIND_RELATION &&
								fc_relkind != RELKIND_MATVIEW &&
								fc_relkind != RELKIND_PARTITIONED_TABLE &&
								fc_relkind != RELKIND_FOREIGN_TABLE)
								elog(ERROR, "unexpected relkind \"%c\" on partition \"%s\"",
									 fc_relkind, fc_stmt->relation->relname);

							if (fc_relkind == RELKIND_FOREIGN_TABLE &&
								(fc_stmt->unique || fc_stmt->primary))
								ereport(ERROR,
										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
										 errmsg("cannot create unique index on partitioned table \"%s\"",
												fc_stmt->relation->relname),
										 errdetail("Table \"%s\" contains partitions that are foreign tables.",
												   fc_stmt->relation->relname)));
						}
						list_free(fc_inheritors);
					}

					/*
					 * 如果 IndexStmt 已经被转换，它必须来自
					 * generateClonedIndexStmt，按当前
					 * 用法，这意味着它来自 expandTableLikeClause
					 * 而不是来自原始解析分析。
					 * 这意味着我们必须将其视为 ALTER TABLE ADD INDEX,
					 * 而不是 CREATE。
					 * （这有点麻烦，但目前似乎不值得为此添加一个单独的布尔字段。）
					 */
					fc_is_alter_table = fc_stmt->transformed;

					/* 运行解析分析... */
					fc_stmt = transformIndexStmt(fc_relid, fc_stmt, fc_queryString);

					/* ... 并执行此操作 */
					EventTriggerAlterTableStart(fc_parsetree);
					fc_address =
						DefineIndex(fc_relid,	/* 堆关系的 OID */
									fc_stmt,
									InvalidOid, /* 没有预定义的 OID */
									InvalidOid, /* 没有父索引 */
									InvalidOid, /* 没有父约束 */
									fc_is_alter_table,
									true,	/* check_rights */
									true,	/* check_not_in_use */
									false,	/* skip_build */
									false); /* quiet */

					/*
					 * 立即将 CREATE INDEX 节点本身添加到存储中；
					 * 如果在 ALTER TABLE
					 * 代码中存储了任何命令，我们需要它们出现在这个命令之后。
					 */
					EventTriggerCollectSimpleCommand(fc_address, fc_secondaryObject,
													 fc_parsetree);
					fc_commandCollected = true;
					EventTriggerAlterTableEnd();
				}
				break;

			case T_CreateExtensionStmt:
				fc_address = CreateExtension(fc_pstate, (CreateExtensionStmt *) fc_parsetree);
				break;

			case T_AlterExtensionStmt:
				fc_address = ExecAlterExtensionStmt(fc_pstate, (AlterExtensionStmt *) fc_parsetree);
				break;

			case T_AlterExtensionContentsStmt:
				fc_address = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) fc_parsetree,
														 &fc_secondaryObject);
				break;

			case T_CreateFdwStmt:
				fc_address = CreateForeignDataWrapper(fc_pstate, (CreateFdwStmt *) fc_parsetree);
				break;

			case T_AlterFdwStmt:
				fc_address = AlterForeignDataWrapper(fc_pstate, (AlterFdwStmt *) fc_parsetree);
				break;

			case T_CreateForeignServerStmt:
				fc_address = CreateForeignServer((CreateForeignServerStmt *) fc_parsetree);
				break;

			case T_AlterForeignServerStmt:
				fc_address = AlterForeignServer((AlterForeignServerStmt *) fc_parsetree);
				break;

			case T_CreateUserMappingStmt:
				fc_address = CreateUserMapping((CreateUserMappingStmt *) fc_parsetree);
				break;

			case T_AlterUserMappingStmt:
				fc_address = AlterUserMapping((AlterUserMappingStmt *) fc_parsetree);
				break;

			case T_DropUserMappingStmt:
				RemoveUserMapping((DropUserMappingStmt *) fc_parsetree);
				/* DROP 不存储命令 */
				fc_commandCollected = true;
				break;

			case T_ImportForeignSchemaStmt:
				ImportForeignSchema((ImportForeignSchemaStmt *) fc_parsetree);
				/* 在 ImportForeignSchema 内部存储命令 */
				fc_commandCollected = true;
				break;

			case T_CompositeTypeStmt:	/* CREATE TYPE (复合类型) */
				{
					CompositeTypeStmt *fc_stmt = (CompositeTypeStmt *) fc_parsetree;

					fc_address = DefineCompositeType(fc_stmt->typevar,
												  fc_stmt->coldeflist);
				}
				break;

			case T_CreateEnumStmt:	/* CREATE TYPE AS ENUM */
				fc_address = DefineEnum((CreateEnumStmt *) fc_parsetree);
				break;

			case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */
				fc_address = DefineRange(fc_pstate, (CreateRangeStmt *) fc_parsetree);
				break;

			case T_AlterEnumStmt:	/* 更改类型 (枚举) */
				fc_address = AlterEnum((AlterEnumStmt *) fc_parsetree);
				break;

			case T_ViewStmt:	/* 创建视图 */
				EventTriggerAlterTableStart(fc_parsetree);
				fc_address = DefineView((ViewStmt *) fc_parsetree, fc_queryString,
									 fc_pstmt->stmt_location, fc_pstmt->stmt_len);
				EventTriggerCollectSimpleCommand(fc_address, fc_secondaryObject,
												 fc_parsetree);
				/* 内部存储 */
				fc_commandCollected = true;
				EventTriggerAlterTableEnd();
				break;

			case T_CreateFunctionStmt:	/* 创建函数 */
				fc_address = CreateFunction(fc_pstate, (CreateFunctionStmt *) fc_parsetree);
				break;

			case T_AlterFunctionStmt:	/* 更改函数 */
				fc_address = AlterFunction(fc_pstate, (AlterFunctionStmt *) fc_parsetree);
				break;

			case T_RuleStmt:	/* 创建规则 */
				fc_address = DefineRule((RuleStmt *) fc_parsetree, fc_queryString);
				break;

			case T_CreateSeqStmt:
				fc_address = DefineSequence(fc_pstate, (CreateSeqStmt *) fc_parsetree);
				break;

			case T_AlterSeqStmt:
				fc_address = AlterSequence(fc_pstate, (AlterSeqStmt *) fc_parsetree);
				break;

			case T_CreateTableAsStmt:
				fc_address = ExecCreateTableAs(fc_pstate, (CreateTableAsStmt *) fc_parsetree,
											fc_params, fc_queryEnv, fc_qc);
				break;

			case T_RefreshMatViewStmt:

				/*
				 * 并发刷新内部执行一些 DDL 命令。
				 * 在此处禁止 DDL 命令收集，以避免这些命令
				 * 出现在解析后的命令队列中。刷新
				 * 命令本身被排队，这就足够了。
				 */
				EventTriggerInhibitCommandCollection();
				PG_TRY();
				{
					fc_address = ExecRefreshMatView((RefreshMatViewStmt *) fc_parsetree,
												 fc_queryString, fc_params, fc_qc);
				}
				PG_FINALLY();
				{
					EventTriggerUndoInhibitCommandCollection();
				}
				PG_END_TRY();
				break;

			case T_CreateTrigStmt:
				fc_address = CreateTrigger((CreateTrigStmt *) fc_parsetree,
										fc_queryString, InvalidOid, InvalidOid,
										InvalidOid, InvalidOid, InvalidOid,
										InvalidOid, NULL, false, false);
				break;

			case T_CreatePLangStmt:
				fc_address = CreateProceduralLanguage((CreatePLangStmt *) fc_parsetree);
				break;

			case T_CreateDomainStmt:
				fc_address = DefineDomain((CreateDomainStmt *) fc_parsetree);
				break;

			case T_CreateConversionStmt:
				fc_address = CreateConversionCommand((CreateConversionStmt *) fc_parsetree);
				break;

			case T_CreateCastStmt:
				fc_address = CreateCast((CreateCastStmt *) fc_parsetree);
				break;

			case T_CreateOpClassStmt:
				DefineOpClass((CreateOpClassStmt *) fc_parsetree);
				/* 命令存储在 DefineOpClass 中 */
				fc_commandCollected = true;
				break;

			case T_CreateOpFamilyStmt:
				fc_address = DefineOpFamily((CreateOpFamilyStmt *) fc_parsetree);

				/*
				 * DefineOpFamily 直接调用 EventTriggerCollectSimpleCommand
				 */
				fc_commandCollected = true;
				break;

			case T_CreateTransformStmt:
				fc_address = CreateTransform((CreateTransformStmt *) fc_parsetree);
				break;

			case T_AlterOpFamilyStmt:
				AlterOpFamily((AlterOpFamilyStmt *) fc_parsetree);
				/* 命令存储在 AlterOpFamily 中 */
				fc_commandCollected = true;
				break;

			case T_AlterTSDictionaryStmt:
				fc_address = AlterTSDictionary((AlterTSDictionaryStmt *) fc_parsetree);
				break;

			case T_AlterTSConfigurationStmt:
				AlterTSConfiguration((AlterTSConfigurationStmt *) fc_parsetree);

				/*
				 * 命令存储在 MakeConfigurationMapping 和
				 * DropConfigurationMapping 中，这些函数从
				 * AlterTSConfiguration 中调用
				 */
				fc_commandCollected = true;
				break;

			case T_AlterTableMoveAllStmt:
				AlterTableMoveAll((AlterTableMoveAllStmt *) fc_parsetree);
				/* 命令存储在 AlterTableMoveAll 中 */
				fc_commandCollected = true;
				break;

			case T_DropStmt:
				fc_ExecDropStmt((DropStmt *) fc_parsetree, fc_isTopLevel);
				/* DROP 不存储命令 */
				fc_commandCollected = true;
				break;

			case T_RenameStmt:
				fc_address = ExecRenameStmt((RenameStmt *) fc_parsetree);
				break;

			case T_AlterObjectDependsStmt:
				fc_address =
					ExecAlterObjectDependsStmt((AlterObjectDependsStmt *) fc_parsetree,
											   &fc_secondaryObject);
				break;

			case T_AlterObjectSchemaStmt:
				fc_address =
					ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) fc_parsetree,
											  &fc_secondaryObject);
				break;

			case T_AlterOwnerStmt:
				fc_address = ExecAlterOwnerStmt((AlterOwnerStmt *) fc_parsetree);
				break;

			case T_AlterOperatorStmt:
				fc_address = AlterOperator((AlterOperatorStmt *) fc_parsetree);
				break;

			case T_AlterTypeStmt:
				fc_address = AlterType((AlterTypeStmt *) fc_parsetree);
				break;

			case T_CommentStmt:
				fc_address = CommentObject((CommentStmt *) fc_parsetree);
				break;

			case T_GrantStmt:
				ExecuteGrantStmt((GrantStmt *) fc_parsetree);
				/* 命令存储在 ExecGrantStmt_oids 中 */
				fc_commandCollected = true;
				break;

			case T_DropOwnedStmt:
				DropOwnedObjects((DropOwnedStmt *) fc_parsetree);
				/* DROP 不存储命令 */
				fc_commandCollected = true;
				break;

			case T_AlterDefaultPrivilegesStmt:
				ExecAlterDefaultPrivilegesStmt(fc_pstate, (AlterDefaultPrivilegesStmt *) fc_parsetree);
				EventTriggerCollectAlterDefPrivs((AlterDefaultPrivilegesStmt *) fc_parsetree);
				fc_commandCollected = true;
				break;

			case T_CreatePolicyStmt:	/* 创建策略 */
				fc_address = CreatePolicy((CreatePolicyStmt *) fc_parsetree);
				break;

			case T_AlterPolicyStmt: /* 更改策略 */
				fc_address = AlterPolicy((AlterPolicyStmt *) fc_parsetree);
				break;

			case T_SecLabelStmt:
				fc_address = ExecSecLabelStmt((SecLabelStmt *) fc_parsetree);
				break;

			case T_CreateAmStmt:
				fc_address = CreateAccessMethod((CreateAmStmt *) fc_parsetree);
				break;

			case T_CreatePublicationStmt:
				fc_address = CreatePublication(fc_pstate, (CreatePublicationStmt *) fc_parsetree);
				break;

			case T_AlterPublicationStmt:
				AlterPublication(fc_pstate, (AlterPublicationStmt *) fc_parsetree);

				/*
				 * AlterPublication 直接调用 EventTriggerCollectSimpleCommand
				 */
				fc_commandCollected = true;
				break;

			case T_CreateSubscriptionStmt:
				fc_address = CreateSubscription(fc_pstate,
											 (CreateSubscriptionStmt *) fc_parsetree,
											 fc_isTopLevel);
				break;

			case T_AlterSubscriptionStmt:
				fc_address = AlterSubscription(fc_pstate,
											(AlterSubscriptionStmt *) fc_parsetree,
											fc_isTopLevel);
				break;

			case T_DropSubscriptionStmt:
				DropSubscription((DropSubscriptionStmt *) fc_parsetree, fc_isTopLevel);
				/* DROP 不存储命令 */
				fc_commandCollected = true;
				break;

			case T_CreateStatsStmt:
				{
					Oid			fc_relid;
					CreateStatsStmt *fc_stmt = (CreateStatsStmt *) fc_parsetree;
					RangeVar   *fc_rel = (RangeVar *) linitial(fc_stmt->relations);

					if (!IsA(fc_rel, RangeVar))
						ereport(ERROR,
								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
								 errmsg("only a single relation is allowed in CREATE STATISTICS")));

					/*
					 * 创建统计信息将影响未来的执行计划
					 * 但不会干扰当前正在执行的计划。
					 * 所以在关系上获得 ShareUpdateExclusiveLock
					 * 应该就足够，冲突的有 ANALYZE 和其他
					 * 设置统计信息的 DDL，但不与正常
					 * 查询冲突。
					 *
					 * XXX 此处不需要 RangeVarCallbackOwnsRelation，以
					 * 保持与之前相同的行为。
					 */
					fc_relid = RangeVarGetRelid(fc_rel, ShareUpdateExclusiveLock, false);

					/* 运行解析分析... */
					fc_stmt = transformStatsStmt(fc_relid, fc_stmt, fc_queryString);

					fc_address = CreateStatistics(fc_stmt);
				}
				break;

			case T_AlterStatsStmt:
				fc_address = AlterStatistics((AlterStatsStmt *) fc_parsetree);
				break;

			case T_AlterCollationStmt:
				fc_address = AlterCollation((AlterCollationStmt *) fc_parsetree);
				break;

			default:
				elog(ERROR, "unrecognized node type: %d",
					 (int) nodeTag(fc_parsetree));
				break;
		}

		/*
		 * 记住该对象，以便 ddl_command_end 事件触发器可以
		 * 访问它。
		 */
		if (!fc_commandCollected)
			EventTriggerCollectSimpleCommand(fc_address, fc_secondaryObject,
											 fc_parsetree);

		if (fc_isCompleteQuery)
		{
			EventTriggerSQLDrop(fc_parsetree);
			EventTriggerDDLCommandEnd(fc_parsetree);
		}
	}
	PG_FINALLY();
	{
		if (fc_needCleanup)
			EventTriggerEndCompleteQuery();
	}
	PG_END_TRY();
}

/*
 * ProcessUtilityForAlterTable
 *		来自 ALTER TABLE 的递归入口
 *
 * ALTER TABLE 有时会生成诸如创建索引的子命令。
 * 它调用这个，而不是主要入口点 ProcessUtility，来执行
 * 这样的子命令。
 *
 * stmt: 要执行的实用程序命令
 * context: 具有我们需要的信息的不透明传递结构
 *
 * 如果需要，调用者有责任在调用此函数后
 * 执行 CommandCounterIncrement。
 */
void ProcessUtilityForAlterTable(Node *fc_stmt, AlterTableUtilityContext *fc_context)
{
	PlannedStmt *fc_wrapper;

	/*
	 * 对于事件触发器，我们必须“关闭”当前复杂命令集，
	 * 然后开始一个新的命令集；这对于确保命令事件的顺序
	 * 与它们被执行的方式一致是必要的。
	 */
	EventTriggerAlterTableEnd();

	/* 创建一个合适的包装 */
	fc_wrapper = makeNode(PlannedStmt);
	fc_wrapper->commandType = CMD_UTILITY;
	fc_wrapper->canSetTag = false;
	fc_wrapper->utilityStmt = fc_stmt;
	fc_wrapper->stmt_location = fc_context->pstmt->stmt_location;
	fc_wrapper->stmt_len = fc_context->pstmt->stmt_len;

	ProcessUtility(fc_wrapper,
				   fc_context->queryString,
				   false,
				   PROCESS_UTILITY_SUBCOMMAND,
				   fc_context->params,
				   fc_context->queryEnv,
				   None_Receiver,
				   NULL);

	EventTriggerAlterTableStart(fc_context->pstmt->utilityStmt);
	EventTriggerAlterTableRelid(fc_context->relid);
}

/*
 * DropStmt 的分发函数
 */
static void fc_ExecDropStmt(DropStmt *fc_stmt, bool fc_isTopLevel)
{
	switch (fc_stmt->removeType)
	{
		case OBJECT_INDEX:
			if (fc_stmt->concurrent)
				PreventInTransactionBlock(fc_isTopLevel,
										  "DROP INDEX CONCURRENTLY");
			/* fall through */

		case OBJECT_TABLE:
		case OBJECT_SEQUENCE:
		case OBJECT_VIEW:
		case OBJECT_MATVIEW:
		case OBJECT_FOREIGN_TABLE:
			RemoveRelations(fc_stmt);
			break;
		default:
			RemoveObjects(fc_stmt);
			break;
	}
}


/*
 * UtilityReturnsTuples
 *		如果这个实用程序语句将输出发送到目标，则返回 “true”。
 *
 * 通常，这里应该有一个针对 ProcessUtility 中每个情况的案例，
 * 其中“dest”被传递下去。
 */
bool UtilityReturnsTuples(Node *fc_parsetree)
{
	switch (nodeTag(fc_parsetree))
	{
		case T_CallStmt:
			{
				CallStmt   *fc_stmt = (CallStmt *) fc_parsetree;

				return (fc_stmt->funcexpr->funcresulttype == RECORDOID);
			}
		case T_FetchStmt:
			{
				FetchStmt  *fc_stmt = (FetchStmt *) fc_parsetree;
				Portal		fc_portal;

				if (fc_stmt->ismove)
					return false;
				fc_portal = GetPortalByName(fc_stmt->portalname);
				if (!PortalIsValid(fc_portal))
					return false;	/* 不是我们该关注的错误 */
				return fc_portal->tupDesc ? true : false;
			}

		case T_ExecuteStmt:
			{
				ExecuteStmt *fc_stmt = (ExecuteStmt *) fc_parsetree;
				PreparedStatement *fc_entry;

				fc_entry = FetchPreparedStatement(fc_stmt->name, false);
				if (!fc_entry)
					return false;	/* 不是我们该关注的错误 */
				if (fc_entry->plansource->resultDesc)
					return true;
				return false;
			}

		case T_ExplainStmt:
			return true;

		case T_VariableShowStmt:
			return true;

		default:
			return false;
	}
}

/*
 * UtilityTupleDescriptor
 *		获取一个实用程序语句的实际输出元组描述符，
 *		该语句之前的 UtilityReturnsTuples() 返回过 “true”。
 *
 * 返回的描述符是在当前内存上下文中创建（或复制） 的。
 */
TupleDesc UtilityTupleDescriptor(Node *fc_parsetree)
{
	switch (nodeTag(fc_parsetree))
	{
		case T_CallStmt:
			return CallStmtResultDesc((CallStmt *) fc_parsetree);

		case T_FetchStmt:
			{
				FetchStmt  *fc_stmt = (FetchStmt *) fc_parsetree;
				Portal		fc_portal;

				if (fc_stmt->ismove)
					return NULL;
				fc_portal = GetPortalByName(fc_stmt->portalname);
				if (!PortalIsValid(fc_portal))
					return NULL;	/* 不是我们该关注的错误 */
				return CreateTupleDescCopy(fc_portal->tupDesc);
			}

		case T_ExecuteStmt:
			{
				ExecuteStmt *fc_stmt = (ExecuteStmt *) fc_parsetree;
				PreparedStatement *fc_entry;

				fc_entry = FetchPreparedStatement(fc_stmt->name, false);
				if (!fc_entry)
					return NULL;	/* 不是我们该关注的错误 */
				return FetchPreparedStatementResultDesc(fc_entry);
			}

		case T_ExplainStmt:
			return ExplainResultDesc((ExplainStmt *) fc_parsetree);

		case T_VariableShowStmt:
			{
				VariableShowStmt *fc_n = (VariableShowStmt *) fc_parsetree;

				return GetPGVariableResultDesc(fc_n->name);
			}

		default:
			return NULL;
	}
}


/*
 * QueryReturnsTuples
 *		如果此查询将输出发送到目标，则返回 “true”。
 */
#ifdef NOT_USED
bool QueryReturnsTuples(Query *fc_parsetree)
{
	switch (fc_parsetree->commandType)
	{
		case CMD_SELECT:
			/* 返回元组 */
			return true;
		case CMD_MERGE:
			return false;
		case CMD_INSERT:
		case CMD_UPDATE:
		case CMD_DELETE:
			/* 带有 RETURNING 的表单返回元组 */
			if (fc_parsetree->returningList)
				return true;
			break;
		case CMD_UTILITY:
			return UtilityReturnsTuples(fc_parsetree->utilityStmt);
		case CMD_UNKNOWN:
		case CMD_NOTHING:
			/* 可能不应该到达这里 */
			break;
	}
	return false;				/* 默认 */
}
#endif


/*
 * UtilityContainsQuery
 *		返回包含的查询，如果没有则返回 NULL
 *
 * 某些实用程序语句，如 EXPLAIN，包含一个可规划的查询。
 * 该函数封装了确切的哪些语句具有查询的知识。
 * 我们假设它仅在已经解析分析的语句上被调用
 * （否则包含的解析树尚不是查询）。
 *
 * 在某些情况下（目前仅限于 CREATE TABLE AS/SELECT INTO 和
 * CREATE MATERIALIZED VIEW 的 EXPLAIN），潜在的包含查询的
 * 实用程序语句可以嵌套。该函数将深入到一个非实用程序查询，
 * 或者如果没有则返回 NULL。
 */
Query * UtilityContainsQuery(Node *fc_parsetree)
{
	Query	   *fc_qry;

	switch (nodeTag(fc_parsetree))
	{
		case T_DeclareCursorStmt:
			fc_qry = castNode(Query, ((DeclareCursorStmt *) fc_parsetree)->query);
			if (fc_qry->commandType == CMD_UTILITY)
				return UtilityContainsQuery(fc_qry->utilityStmt);
			return fc_qry;

		case T_ExplainStmt:
			fc_qry = castNode(Query, ((ExplainStmt *) fc_parsetree)->query);
			if (fc_qry->commandType == CMD_UTILITY)
				return UtilityContainsQuery(fc_qry->utilityStmt);
			return fc_qry;

		case T_CreateTableAsStmt:
			fc_qry = castNode(Query, ((CreateTableAsStmt *) fc_parsetree)->query);
			if (fc_qry->commandType == CMD_UTILITY)
				return UtilityContainsQuery(fc_qry->utilityStmt);
			return fc_qry;

		default:
			return NULL;
	}
}


/*
 * AlterObjectTypeCommandTag
 *		CreateCommandTag 的辅助函数
 *
 * 这涵盖了大多数使用 ObjectType 枚举的 ALTER 情况。
 */
static CommandTag
fc_AlterObjectTypeCommandTag(ObjectType fc_objtype)
{
	CommandTag	fc_tag;

	switch (fc_objtype)
	{
		case OBJECT_AGGREGATE:
			fc_tag = CMDTAG_ALTER_AGGREGATE;
			break;
		case OBJECT_ATTRIBUTE:
			fc_tag = CMDTAG_ALTER_TYPE;
			break;
		case OBJECT_CAST:
			fc_tag = CMDTAG_ALTER_CAST;
			break;
		case OBJECT_COLLATION:
			fc_tag = CMDTAG_ALTER_COLLATION;
			break;
		case OBJECT_COLUMN:
			fc_tag = CMDTAG_ALTER_TABLE;
			break;
		case OBJECT_CONVERSION:
			fc_tag = CMDTAG_ALTER_CONVERSION;
			break;
		case OBJECT_DATABASE:
			fc_tag = CMDTAG_ALTER_DATABASE;
			break;
		case OBJECT_DOMAIN:
		case OBJECT_DOMCONSTRAINT:
			fc_tag = CMDTAG_ALTER_DOMAIN;
			break;
		case OBJECT_EXTENSION:
			fc_tag = CMDTAG_ALTER_EXTENSION;
			break;
		case OBJECT_FDW:
			fc_tag = CMDTAG_ALTER_FOREIGN_DATA_WRAPPER;
			break;
		case OBJECT_FOREIGN_SERVER:
			fc_tag = CMDTAG_ALTER_SERVER;
			break;
		case OBJECT_FOREIGN_TABLE:
			fc_tag = CMDTAG_ALTER_FOREIGN_TABLE;
			break;
		case OBJECT_FUNCTION:
			fc_tag = CMDTAG_ALTER_FUNCTION;
			break;
		case OBJECT_INDEX:
			fc_tag = CMDTAG_ALTER_INDEX;
			break;
		case OBJECT_LANGUAGE:
			fc_tag = CMDTAG_ALTER_LANGUAGE;
			break;
		case OBJECT_LARGEOBJECT:
			fc_tag = CMDTAG_ALTER_LARGE_OBJECT;
			break;
		case OBJECT_OPCLASS:
			fc_tag = CMDTAG_ALTER_OPERATOR_CLASS;
			break;
		case OBJECT_OPERATOR:
			fc_tag = CMDTAG_ALTER_OPERATOR;
			break;
		case OBJECT_OPFAMILY:
			fc_tag = CMDTAG_ALTER_OPERATOR_FAMILY;
			break;
		case OBJECT_POLICY:
			fc_tag = CMDTAG_ALTER_POLICY;
			break;
		case OBJECT_PROCEDURE:
			fc_tag = CMDTAG_ALTER_PROCEDURE;
			break;
		case OBJECT_ROLE:
			fc_tag = CMDTAG_ALTER_ROLE;
			break;
		case OBJECT_ROUTINE:
			fc_tag = CMDTAG_ALTER_ROUTINE;
			break;
		case OBJECT_RULE:
			fc_tag = CMDTAG_ALTER_RULE;
			break;
		case OBJECT_SCHEMA:
			fc_tag = CMDTAG_ALTER_SCHEMA;
			break;
		case OBJECT_SEQUENCE:
			fc_tag = CMDTAG_ALTER_SEQUENCE;
			break;
		case OBJECT_TABLE:
		case OBJECT_TABCONSTRAINT:
			fc_tag = CMDTAG_ALTER_TABLE;
			break;
		case OBJECT_TABLESPACE:
			fc_tag = CMDTAG_ALTER_TABLESPACE;
			break;
		case OBJECT_TRIGGER:
			fc_tag = CMDTAG_ALTER_TRIGGER;
			break;
		case OBJECT_EVENT_TRIGGER:
			fc_tag = CMDTAG_ALTER_EVENT_TRIGGER;
			break;
		case OBJECT_TSCONFIGURATION:
			fc_tag = CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION;
			break;
		case OBJECT_TSDICTIONARY:
			fc_tag = CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY;
			break;
		case OBJECT_TSPARSER:
			fc_tag = CMDTAG_ALTER_TEXT_SEARCH_PARSER;
			break;
		case OBJECT_TSTEMPLATE:
			fc_tag = CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE;
			break;
		case OBJECT_TYPE:
			fc_tag = CMDTAG_ALTER_TYPE;
			break;
		case OBJECT_VIEW:
			fc_tag = CMDTAG_ALTER_VIEW;
			break;
		case OBJECT_MATVIEW:
			fc_tag = CMDTAG_ALTER_MATERIALIZED_VIEW;
			break;
		case OBJECT_PUBLICATION:
			fc_tag = CMDTAG_ALTER_PUBLICATION;
			break;
		case OBJECT_SUBSCRIPTION:
			fc_tag = CMDTAG_ALTER_SUBSCRIPTION;
			break;
		case OBJECT_STATISTIC_EXT:
			fc_tag = CMDTAG_ALTER_STATISTICS;
			break;
		default:
			fc_tag = CMDTAG_UNKNOWN;
			break;
	}

	return fc_tag;
}

/*
 * CreateCommandTag
 *		用于获取命令操作的 CommandTag，
 *		给定原始（未经分析的）解析树、已分析的查询
 *		或计划语句。
 *
 * 这必须处理所有命令类型，但由于绝大多数
 * 都是实用命令，因此在这里保留它是合理的。
 */
CommandTag
CreateCommandTag(Node *fc_parsetree)
{
	CommandTag	fc_tag;

	switch (nodeTag(fc_parsetree))
	{
			/* 如果给定的是 RawStmt，则递归 */
		case T_RawStmt:
			fc_tag = CreateCommandTag(((RawStmt *) fc_parsetree)->stmt);
			break;

			/* 原始可计划查询 */
		case T_InsertStmt:
			fc_tag = CMDTAG_INSERT;
			break;

		case T_DeleteStmt:
			fc_tag = CMDTAG_DELETE;
			break;

		case T_UpdateStmt:
			fc_tag = CMDTAG_UPDATE;
			break;

		case T_MergeStmt:
			fc_tag = CMDTAG_MERGE;
			break;

		case T_SelectStmt:
			fc_tag = CMDTAG_SELECT;
			break;

		case T_PLAssignStmt:
			fc_tag = CMDTAG_SELECT;
			break;

			/* 实用语句 --- 无论是原始还是成熟都相同 */
		case T_TransactionStmt:
			{
				TransactionStmt *fc_stmt = (TransactionStmt *) fc_parsetree;

				switch (fc_stmt->kind)
				{
					case TRANS_STMT_BEGIN:
						fc_tag = CMDTAG_BEGIN;
						break;

					case TRANS_STMT_START:
						fc_tag = CMDTAG_START_TRANSACTION;
						break;

					case TRANS_STMT_COMMIT:
						fc_tag = CMDTAG_COMMIT;
						break;

					case TRANS_STMT_ROLLBACK:
					case TRANS_STMT_ROLLBACK_TO:
						fc_tag = CMDTAG_ROLLBACK;
						break;

					case TRANS_STMT_SAVEPOINT:
						fc_tag = CMDTAG_SAVEPOINT;
						break;

					case TRANS_STMT_RELEASE:
						fc_tag = CMDTAG_RELEASE;
						break;

					case TRANS_STMT_PREPARE:
						fc_tag = CMDTAG_PREPARE_TRANSACTION;
						break;

					case TRANS_STMT_COMMIT_PREPARED:
						fc_tag = CMDTAG_COMMIT_PREPARED;
						break;

					case TRANS_STMT_ROLLBACK_PREPARED:
						fc_tag = CMDTAG_ROLLBACK_PREPARED;
						break;

					default:
						fc_tag = CMDTAG_UNKNOWN;
						break;
				}
			}
			break;

		case T_DeclareCursorStmt:
			fc_tag = CMDTAG_DECLARE_CURSOR;
			break;

		case T_ClosePortalStmt:
			{
				ClosePortalStmt *fc_stmt = (ClosePortalStmt *) fc_parsetree;

				if (fc_stmt->portalname == NULL)
					fc_tag = CMDTAG_CLOSE_CURSOR_ALL;
				else
					fc_tag = CMDTAG_CLOSE_CURSOR;
			}
			break;

		case T_FetchStmt:
			{
				FetchStmt  *fc_stmt = (FetchStmt *) fc_parsetree;

				fc_tag = (fc_stmt->ismove) ? CMDTAG_MOVE : CMDTAG_FETCH;
			}
			break;

		case T_CreateDomainStmt:
			fc_tag = CMDTAG_CREATE_DOMAIN;
			break;

		case T_CreateSchemaStmt:
			fc_tag = CMDTAG_CREATE_SCHEMA;
			break;

		case T_CreateStmt:
			fc_tag = CMDTAG_CREATE_TABLE;
			break;

		case T_CreateTableSpaceStmt:
			fc_tag = CMDTAG_CREATE_TABLESPACE;
			break;

		case T_DropTableSpaceStmt:
			fc_tag = CMDTAG_DROP_TABLESPACE;
			break;

		case T_AlterTableSpaceOptionsStmt:
			fc_tag = CMDTAG_ALTER_TABLESPACE;
			break;

		case T_CreateExtensionStmt:
			fc_tag = CMDTAG_CREATE_EXTENSION;
			break;

		case T_AlterExtensionStmt:
			fc_tag = CMDTAG_ALTER_EXTENSION;
			break;

		case T_AlterExtensionContentsStmt:
			fc_tag = CMDTAG_ALTER_EXTENSION;
			break;

		case T_CreateFdwStmt:
			fc_tag = CMDTAG_CREATE_FOREIGN_DATA_WRAPPER;
			break;

		case T_AlterFdwStmt:
			fc_tag = CMDTAG_ALTER_FOREIGN_DATA_WRAPPER;
			break;

		case T_CreateForeignServerStmt:
			fc_tag = CMDTAG_CREATE_SERVER;
			break;

		case T_AlterForeignServerStmt:
			fc_tag = CMDTAG_ALTER_SERVER;
			break;

		case T_CreateUserMappingStmt:
			fc_tag = CMDTAG_CREATE_USER_MAPPING;
			break;

		case T_AlterUserMappingStmt:
			fc_tag = CMDTAG_ALTER_USER_MAPPING;
			break;

		case T_DropUserMappingStmt:
			fc_tag = CMDTAG_DROP_USER_MAPPING;
			break;

		case T_CreateForeignTableStmt:
			fc_tag = CMDTAG_CREATE_FOREIGN_TABLE;
			break;

		case T_ImportForeignSchemaStmt:
			fc_tag = CMDTAG_IMPORT_FOREIGN_SCHEMA;
			break;

		case T_DropStmt:
			switch (((DropStmt *) fc_parsetree)->removeType)
			{
				case OBJECT_TABLE:
					fc_tag = CMDTAG_DROP_TABLE;
					break;
				case OBJECT_SEQUENCE:
					fc_tag = CMDTAG_DROP_SEQUENCE;
					break;
				case OBJECT_VIEW:
					fc_tag = CMDTAG_DROP_VIEW;
					break;
				case OBJECT_MATVIEW:
					fc_tag = CMDTAG_DROP_MATERIALIZED_VIEW;
					break;
				case OBJECT_INDEX:
					fc_tag = CMDTAG_DROP_INDEX;
					break;
				case OBJECT_TYPE:
					fc_tag = CMDTAG_DROP_TYPE;
					break;
				case OBJECT_DOMAIN:
					fc_tag = CMDTAG_DROP_DOMAIN;
					break;
				case OBJECT_COLLATION:
					fc_tag = CMDTAG_DROP_COLLATION;
					break;
				case OBJECT_CONVERSION:
					fc_tag = CMDTAG_DROP_CONVERSION;
					break;
				case OBJECT_SCHEMA:
					fc_tag = CMDTAG_DROP_SCHEMA;
					break;
				case OBJECT_TSPARSER:
					fc_tag = CMDTAG_DROP_TEXT_SEARCH_PARSER;
					break;
				case OBJECT_TSDICTIONARY:
					fc_tag = CMDTAG_DROP_TEXT_SEARCH_DICTIONARY;
					break;
				case OBJECT_TSTEMPLATE:
					fc_tag = CMDTAG_DROP_TEXT_SEARCH_TEMPLATE;
					break;
				case OBJECT_TSCONFIGURATION:
					fc_tag = CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION;
					break;
				case OBJECT_FOREIGN_TABLE:
					fc_tag = CMDTAG_DROP_FOREIGN_TABLE;
					break;
				case OBJECT_EXTENSION:
					fc_tag = CMDTAG_DROP_EXTENSION;
					break;
				case OBJECT_FUNCTION:
					fc_tag = CMDTAG_DROP_FUNCTION;
					break;
				case OBJECT_PROCEDURE:
					fc_tag = CMDTAG_DROP_PROCEDURE;
					break;
				case OBJECT_ROUTINE:
					fc_tag = CMDTAG_DROP_ROUTINE;
					break;
				case OBJECT_AGGREGATE:
					fc_tag = CMDTAG_DROP_AGGREGATE;
					break;
				case OBJECT_OPERATOR:
					fc_tag = CMDTAG_DROP_OPERATOR;
					break;
				case OBJECT_LANGUAGE:
					fc_tag = CMDTAG_DROP_LANGUAGE;
					break;
				case OBJECT_CAST:
					fc_tag = CMDTAG_DROP_CAST;
					break;
				case OBJECT_TRIGGER:
					fc_tag = CMDTAG_DROP_TRIGGER;
					break;
				case OBJECT_EVENT_TRIGGER:
					fc_tag = CMDTAG_DROP_EVENT_TRIGGER;
					break;
				case OBJECT_RULE:
					fc_tag = CMDTAG_DROP_RULE;
					break;
				case OBJECT_FDW:
					fc_tag = CMDTAG_DROP_FOREIGN_DATA_WRAPPER;
					break;
				case OBJECT_FOREIGN_SERVER:
					fc_tag = CMDTAG_DROP_SERVER;
					break;
				case OBJECT_OPCLASS:
					fc_tag = CMDTAG_DROP_OPERATOR_CLASS;
					break;
				case OBJECT_OPFAMILY:
					fc_tag = CMDTAG_DROP_OPERATOR_FAMILY;
					break;
				case OBJECT_POLICY:
					fc_tag = CMDTAG_DROP_POLICY;
					break;
				case OBJECT_TRANSFORM:
					fc_tag = CMDTAG_DROP_TRANSFORM;
					break;
				case OBJECT_ACCESS_METHOD:
					fc_tag = CMDTAG_DROP_ACCESS_METHOD;
					break;
				case OBJECT_PUBLICATION:
					fc_tag = CMDTAG_DROP_PUBLICATION;
					break;
				case OBJECT_STATISTIC_EXT:
					fc_tag = CMDTAG_DROP_STATISTICS;
					break;
				default:
					fc_tag = CMDTAG_UNKNOWN;
			}
			break;

		case T_TruncateStmt:
			fc_tag = CMDTAG_TRUNCATE_TABLE;
			break;

		case T_CommentStmt:
			fc_tag = CMDTAG_COMMENT;
			break;

		case T_SecLabelStmt:
			fc_tag = CMDTAG_SECURITY_LABEL;
			break;

		case T_CopyStmt:
			fc_tag = CMDTAG_COPY;
			break;

		case T_RenameStmt:

			/*
			 * 当列被重命名时，命令标签是从其
			 * 关系类型创建的
			 */
			fc_tag = fc_AlterObjectTypeCommandTag(((RenameStmt *) fc_parsetree)->renameType == OBJECT_COLUMN ?
											((RenameStmt *) fc_parsetree)->relationType :
											((RenameStmt *) fc_parsetree)->renameType);
			break;

		case T_AlterObjectDependsStmt:
			fc_tag = fc_AlterObjectTypeCommandTag(((AlterObjectDependsStmt *) fc_parsetree)->objectType);
			break;

		case T_AlterObjectSchemaStmt:
			fc_tag = fc_AlterObjectTypeCommandTag(((AlterObjectSchemaStmt *) fc_parsetree)->objectType);
			break;

		case T_AlterOwnerStmt:
			fc_tag = fc_AlterObjectTypeCommandTag(((AlterOwnerStmt *) fc_parsetree)->objectType);
			break;

		case T_AlterTableMoveAllStmt:
			fc_tag = fc_AlterObjectTypeCommandTag(((AlterTableMoveAllStmt *) fc_parsetree)->objtype);
			break;

		case T_AlterTableStmt:
			fc_tag = fc_AlterObjectTypeCommandTag(((AlterTableStmt *) fc_parsetree)->objtype);
			break;

		case T_AlterDomainStmt:
			fc_tag = CMDTAG_ALTER_DOMAIN;
			break;

		case T_AlterFunctionStmt:
			switch (((AlterFunctionStmt *) fc_parsetree)->objtype)
			{
				case OBJECT_FUNCTION:
					fc_tag = CMDTAG_ALTER_FUNCTION;
					break;
				case OBJECT_PROCEDURE:
					fc_tag = CMDTAG_ALTER_PROCEDURE;
					break;
				case OBJECT_ROUTINE:
					fc_tag = CMDTAG_ALTER_ROUTINE;
					break;
				default:
					fc_tag = CMDTAG_UNKNOWN;
			}
			break;

		case T_GrantStmt:
			{
				GrantStmt  *fc_stmt = (GrantStmt *) fc_parsetree;

				fc_tag = (fc_stmt->is_grant) ? CMDTAG_GRANT : CMDTAG_REVOKE;
			}
			break;

		case T_GrantRoleStmt:
			{
				GrantRoleStmt *fc_stmt = (GrantRoleStmt *) fc_parsetree;

				fc_tag = (fc_stmt->is_grant) ? CMDTAG_GRANT_ROLE : CMDTAG_REVOKE_ROLE;
			}
			break;

		case T_AlterDefaultPrivilegesStmt:
			fc_tag = CMDTAG_ALTER_DEFAULT_PRIVILEGES;
			break;

		case T_DefineStmt:
			switch (((DefineStmt *) fc_parsetree)->kind)
			{
				case OBJECT_AGGREGATE:
					fc_tag = CMDTAG_CREATE_AGGREGATE;
					break;
				case OBJECT_OPERATOR:
					fc_tag = CMDTAG_CREATE_OPERATOR;
					break;
				case OBJECT_TYPE:
					fc_tag = CMDTAG_CREATE_TYPE;
					break;
				case OBJECT_TSPARSER:
					fc_tag = CMDTAG_CREATE_TEXT_SEARCH_PARSER;
					break;
				case OBJECT_TSDICTIONARY:
					fc_tag = CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY;
					break;
				case OBJECT_TSTEMPLATE:
					fc_tag = CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE;
					break;
				case OBJECT_TSCONFIGURATION:
					fc_tag = CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION;
					break;
				case OBJECT_COLLATION:
					fc_tag = CMDTAG_CREATE_COLLATION;
					break;
				case OBJECT_ACCESS_METHOD:
					fc_tag = CMDTAG_CREATE_ACCESS_METHOD;
					break;
				default:
					fc_tag = CMDTAG_UNKNOWN;
			}
			break;

		case T_CompositeTypeStmt:
			fc_tag = CMDTAG_CREATE_TYPE;
			break;

		case T_CreateEnumStmt:
			fc_tag = CMDTAG_CREATE_TYPE;
			break;

		case T_CreateRangeStmt:
			fc_tag = CMDTAG_CREATE_TYPE;
			break;

		case T_AlterEnumStmt:
			fc_tag = CMDTAG_ALTER_TYPE;
			break;

		case T_ViewStmt:
			fc_tag = CMDTAG_CREATE_VIEW;
			break;

		case T_CreateFunctionStmt:
			if (((CreateFunctionStmt *) fc_parsetree)->is_procedure)
				fc_tag = CMDTAG_CREATE_PROCEDURE;
			else
				fc_tag = CMDTAG_CREATE_FUNCTION;
			break;

		case T_IndexStmt:
			fc_tag = CMDTAG_CREATE_INDEX;
			break;

		case T_RuleStmt:
			fc_tag = CMDTAG_CREATE_RULE;
			break;

		case T_CreateSeqStmt:
			fc_tag = CMDTAG_CREATE_SEQUENCE;
			break;

		case T_AlterSeqStmt:
			fc_tag = CMDTAG_ALTER_SEQUENCE;
			break;

		case T_DoStmt:
			fc_tag = CMDTAG_DO;
			break;

		case T_CreatedbStmt:
			fc_tag = CMDTAG_CREATE_DATABASE;
			break;

		case T_AlterDatabaseStmt:
		case T_AlterDatabaseRefreshCollStmt:
		case T_AlterDatabaseSetStmt:
			fc_tag = CMDTAG_ALTER_DATABASE;
			break;

		case T_DropdbStmt:
			fc_tag = CMDTAG_DROP_DATABASE;
			break;

		case T_NotifyStmt:
			fc_tag = CMDTAG_NOTIFY;
			break;

		case T_ListenStmt:
			fc_tag = CMDTAG_LISTEN;
			break;

		case T_UnlistenStmt:
			fc_tag = CMDTAG_UNLISTEN;
			break;

		case T_LoadStmt:
			fc_tag = CMDTAG_LOAD;
			break;

		case T_CallStmt:
			fc_tag = CMDTAG_CALL;
			break;

		case T_ClusterStmt:
			fc_tag = CMDTAG_CLUSTER;
			break;

		case T_VacuumStmt:
			if (((VacuumStmt *) fc_parsetree)->is_vacuumcmd)
				fc_tag = CMDTAG_VACUUM;
			else
				fc_tag = CMDTAG_ANALYZE;
			break;

		case T_ExplainStmt:
			fc_tag = CMDTAG_EXPLAIN;
			break;

		case T_CreateTableAsStmt:
			switch (((CreateTableAsStmt *) fc_parsetree)->objtype)
			{
				case OBJECT_TABLE:
					if (((CreateTableAsStmt *) fc_parsetree)->is_select_into)
						fc_tag = CMDTAG_SELECT_INTO;
					else
						fc_tag = CMDTAG_CREATE_TABLE_AS;
					break;
				case OBJECT_MATVIEW:
					fc_tag = CMDTAG_CREATE_MATERIALIZED_VIEW;
					break;
				default:
					fc_tag = CMDTAG_UNKNOWN;
			}
			break;

		case T_RefreshMatViewStmt:
			fc_tag = CMDTAG_REFRESH_MATERIALIZED_VIEW;
			break;

		case T_AlterSystemStmt:
			fc_tag = CMDTAG_ALTER_SYSTEM;
			break;

		case T_VariableSetStmt:
			switch (((VariableSetStmt *) fc_parsetree)->kind)
			{
				case VAR_SET_VALUE:
				case VAR_SET_CURRENT:
				case VAR_SET_DEFAULT:
				case VAR_SET_MULTI:
					fc_tag = CMDTAG_SET;
					break;
				case VAR_RESET:
				case VAR_RESET_ALL:
					fc_tag = CMDTAG_RESET;
					break;
				default:
					fc_tag = CMDTAG_UNKNOWN;
			}
			break;

		case T_VariableShowStmt:
			fc_tag = CMDTAG_SHOW;
			break;

		case T_DiscardStmt:
			switch (((DiscardStmt *) fc_parsetree)->target)
			{
				case DISCARD_ALL:
					fc_tag = CMDTAG_DISCARD_ALL;
					break;
				case DISCARD_PLANS:
					fc_tag = CMDTAG_DISCARD_PLANS;
					break;
				case DISCARD_TEMP:
					fc_tag = CMDTAG_DISCARD_TEMP;
					break;
				case DISCARD_SEQUENCES:
					fc_tag = CMDTAG_DISCARD_SEQUENCES;
					break;
				default:
					fc_tag = CMDTAG_UNKNOWN;
			}
			break;

		case T_CreateTransformStmt:
			fc_tag = CMDTAG_CREATE_TRANSFORM;
			break;

		case T_CreateTrigStmt:
			fc_tag = CMDTAG_CREATE_TRIGGER;
			break;

		case T_CreateEventTrigStmt:
			fc_tag = CMDTAG_CREATE_EVENT_TRIGGER;
			break;

		case T_AlterEventTrigStmt:
			fc_tag = CMDTAG_ALTER_EVENT_TRIGGER;
			break;

		case T_CreatePLangStmt:
			fc_tag = CMDTAG_CREATE_LANGUAGE;
			break;

		case T_CreateRoleStmt:
			fc_tag = CMDTAG_CREATE_ROLE;
			break;

		case T_AlterRoleStmt:
			fc_tag = CMDTAG_ALTER_ROLE;
			break;

		case T_AlterRoleSetStmt:
			fc_tag = CMDTAG_ALTER_ROLE;
			break;

		case T_DropRoleStmt:
			fc_tag = CMDTAG_DROP_ROLE;
			break;

		case T_DropOwnedStmt:
			fc_tag = CMDTAG_DROP_OWNED;
			break;

		case T_ReassignOwnedStmt:
			fc_tag = CMDTAG_REASSIGN_OWNED;
			break;

		case T_LockStmt:
			fc_tag = CMDTAG_LOCK_TABLE;
			break;

		case T_ConstraintsSetStmt:
			fc_tag = CMDTAG_SET_CONSTRAINTS;
			break;

		case T_CheckPointStmt:
			fc_tag = CMDTAG_CHECKPOINT;
			break;

		case T_ReindexStmt:
			fc_tag = CMDTAG_REINDEX;
			break;

		case T_CreateConversionStmt:
			fc_tag = CMDTAG_CREATE_CONVERSION;
			break;

		case T_CreateCastStmt:
			fc_tag = CMDTAG_CREATE_CAST;
			break;

		case T_CreateOpClassStmt:
			fc_tag = CMDTAG_CREATE_OPERATOR_CLASS;
			break;

		case T_CreateOpFamilyStmt:
			fc_tag = CMDTAG_CREATE_OPERATOR_FAMILY;
			break;

		case T_AlterOpFamilyStmt:
			fc_tag = CMDTAG_ALTER_OPERATOR_FAMILY;
			break;

		case T_AlterOperatorStmt:
			fc_tag = CMDTAG_ALTER_OPERATOR;
			break;

		case T_AlterTypeStmt:
			fc_tag = CMDTAG_ALTER_TYPE;
			break;

		case T_AlterTSDictionaryStmt:
			fc_tag = CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY;
			break;

		case T_AlterTSConfigurationStmt:
			fc_tag = CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION;
			break;

		case T_CreatePolicyStmt:
			fc_tag = CMDTAG_CREATE_POLICY;
			break;

		case T_AlterPolicyStmt:
			fc_tag = CMDTAG_ALTER_POLICY;
			break;

		case T_CreateAmStmt:
			fc_tag = CMDTAG_CREATE_ACCESS_METHOD;
			break;

		case T_CreatePublicationStmt:
			fc_tag = CMDTAG_CREATE_PUBLICATION;
			break;

		case T_AlterPublicationStmt:
			fc_tag = CMDTAG_ALTER_PUBLICATION;
			break;

		case T_CreateSubscriptionStmt:
			fc_tag = CMDTAG_CREATE_SUBSCRIPTION;
			break;

		case T_AlterSubscriptionStmt:
			fc_tag = CMDTAG_ALTER_SUBSCRIPTION;
			break;

		case T_DropSubscriptionStmt:
			fc_tag = CMDTAG_DROP_SUBSCRIPTION;
			break;

		case T_AlterCollationStmt:
			fc_tag = CMDTAG_ALTER_COLLATION;
			break;

		case T_PrepareStmt:
			fc_tag = CMDTAG_PREPARE;
			break;

		case T_ExecuteStmt:
			fc_tag = CMDTAG_EXECUTE;
			break;

		case T_CreateStatsStmt:
			fc_tag = CMDTAG_CREATE_STATISTICS;
			break;

		case T_AlterStatsStmt:
			fc_tag = CMDTAG_ALTER_STATISTICS;
			break;

		case T_DeallocateStmt:
			{
				DeallocateStmt *fc_stmt = (DeallocateStmt *) fc_parsetree;

				if (fc_stmt->name == NULL)
					fc_tag = CMDTAG_DEALLOCATE_ALL;
				else
					fc_tag = CMDTAG_DEALLOCATE;
			}
			break;

			/* 已计划查询 */
		case T_PlannedStmt:
			{
				PlannedStmt *fc_stmt = (PlannedStmt *) fc_parsetree;

				switch (fc_stmt->commandType)
				{
					case CMD_SELECT:

						/*
						 * 我们在这里多加一点小心，以便结果
						 * 将对只读
						 * 语句的投诉有用
						 */
						if (fc_stmt->rowMarks != NIL)
						{
							/* 不是100%，但可能足够接近 */
							switch (((PlanRowMark *) linitial(fc_stmt->rowMarks))->strength)
							{
								case LCS_FORKEYSHARE:
									fc_tag = CMDTAG_SELECT_FOR_KEY_SHARE;
									break;
								case LCS_FORSHARE:
									fc_tag = CMDTAG_SELECT_FOR_SHARE;
									break;
								case LCS_FORNOKEYUPDATE:
									fc_tag = CMDTAG_SELECT_FOR_NO_KEY_UPDATE;
									break;
								case LCS_FORUPDATE:
									fc_tag = CMDTAG_SELECT_FOR_UPDATE;
									break;
								default:
									fc_tag = CMDTAG_SELECT;
									break;
							}
						}
						else
							fc_tag = CMDTAG_SELECT;
						break;
					case CMD_UPDATE:
						fc_tag = CMDTAG_UPDATE;
						break;
					case CMD_INSERT:
						fc_tag = CMDTAG_INSERT;
						break;
					case CMD_DELETE:
						fc_tag = CMDTAG_DELETE;
						break;
					case CMD_MERGE:
						fc_tag = CMDTAG_MERGE;
						break;
					case CMD_UTILITY:
						fc_tag = CreateCommandTag(fc_stmt->utilityStmt);
						break;
					default:
						elog(WARNING, "unrecognized commandType: %d",
							 (int) fc_stmt->commandType);
						fc_tag = CMDTAG_UNKNOWN;
						break;
				}
			}
			break;

			/* 已解析并重写但未计划的查询 */
		case T_Query:
			{
				Query	   *fc_stmt = (Query *) fc_parsetree;

				switch (fc_stmt->commandType)
				{
					case CMD_SELECT:

						/*
						 * 我们在这里多加一点小心，以便结果
						 * 将对只读
						 * 语句的投诉有用
						 */
						if (fc_stmt->rowMarks != NIL)
						{
							/* 不是100%，但可能足够接近 */
							switch (((RowMarkClause *) linitial(fc_stmt->rowMarks))->strength)
							{
								case LCS_FORKEYSHARE:
									fc_tag = CMDTAG_SELECT_FOR_KEY_SHARE;
									break;
								case LCS_FORSHARE:
									fc_tag = CMDTAG_SELECT_FOR_SHARE;
									break;
								case LCS_FORNOKEYUPDATE:
									fc_tag = CMDTAG_SELECT_FOR_NO_KEY_UPDATE;
									break;
								case LCS_FORUPDATE:
									fc_tag = CMDTAG_SELECT_FOR_UPDATE;
									break;
								default:
									fc_tag = CMDTAG_UNKNOWN;
									break;
							}
						}
						else
							fc_tag = CMDTAG_SELECT;
						break;
					case CMD_UPDATE:
						fc_tag = CMDTAG_UPDATE;
						break;
					case CMD_INSERT:
						fc_tag = CMDTAG_INSERT;
						break;
					case CMD_DELETE:
						fc_tag = CMDTAG_DELETE;
						break;
					case CMD_MERGE:
						fc_tag = CMDTAG_MERGE;
						break;
					case CMD_UTILITY:
						fc_tag = CreateCommandTag(fc_stmt->utilityStmt);
						break;
					default:
						elog(WARNING, "unrecognized commandType: %d",
							 (int) fc_stmt->commandType);
						fc_tag = CMDTAG_UNKNOWN;
						break;
				}
			}
			break;

		default:
			elog(WARNING, "unrecognized node type: %d",
				 (int) nodeTag(fc_parsetree));
			fc_tag = CMDTAG_UNKNOWN;
			break;
	}

	return fc_tag;
}


/*
 * GetCommandLogLevel
 *		用于获取命令的最低 log_statement 级别，
 *		给定原始（未经分析的）解析树、已分析的查询
 *		或计划语句。
 *
 * 这必须处理所有命令类型，但由于绝大多数
 * 都是实用命令，因此在这里保留它是合理的。
 */
LogStmtLevel
GetCommandLogLevel(Node *fc_parsetree)
{
	LogStmtLevel fc_lev;

	switch (nodeTag(fc_parsetree))
	{
			/* 如果给定的是 RawStmt，则递归 */
		case T_RawStmt:
			fc_lev = GetCommandLogLevel(((RawStmt *) fc_parsetree)->stmt);
			break;

			/* 原始可计划查询 */
		case T_InsertStmt:
		case T_DeleteStmt:
		case T_UpdateStmt:
		case T_MergeStmt:
			fc_lev = LOGSTMT_MOD;
			break;

		case T_SelectStmt:
			if (((SelectStmt *) fc_parsetree)->intoClause)
				fc_lev = LOGSTMT_DDL;	/* SELECT INTO */
			else
				fc_lev = LOGSTMT_ALL;
			break;

		case T_PLAssignStmt:
			fc_lev = LOGSTMT_ALL;
			break;

			/* 实用语句 --- 无论是原始还是成熟都相同 */
		case T_TransactionStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_DeclareCursorStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_ClosePortalStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_FetchStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_CreateSchemaStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateStmt:
		case T_CreateForeignTableStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateTableSpaceStmt:
		case T_DropTableSpaceStmt:
		case T_AlterTableSpaceOptionsStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateExtensionStmt:
		case T_AlterExtensionStmt:
		case T_AlterExtensionContentsStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateFdwStmt:
		case T_AlterFdwStmt:
		case T_CreateForeignServerStmt:
		case T_AlterForeignServerStmt:
		case T_CreateUserMappingStmt:
		case T_AlterUserMappingStmt:
		case T_DropUserMappingStmt:
		case T_ImportForeignSchemaStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_DropStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_TruncateStmt:
			fc_lev = LOGSTMT_MOD;
			break;

		case T_CommentStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_SecLabelStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CopyStmt:
			if (((CopyStmt *) fc_parsetree)->is_from)
				fc_lev = LOGSTMT_MOD;
			else
				fc_lev = LOGSTMT_ALL;
			break;

		case T_PrepareStmt:
			{
				PrepareStmt *fc_stmt = (PrepareStmt *) fc_parsetree;

				/* 查看 PREPARE 中包含的语句 */
				fc_lev = GetCommandLogLevel(fc_stmt->query);
			}
			break;

		case T_ExecuteStmt:
			{
				ExecuteStmt *fc_stmt = (ExecuteStmt *) fc_parsetree;
				PreparedStatement *fc_ps;

				/* 查看 EXECUTE 中引用的语句 */
				fc_ps = FetchPreparedStatement(fc_stmt->name, false);
				if (fc_ps && fc_ps->plansource->raw_parse_tree)
					fc_lev = GetCommandLogLevel(fc_ps->plansource->raw_parse_tree->stmt);
				else
					fc_lev = LOGSTMT_ALL;
			}
			break;

		case T_DeallocateStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_RenameStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterObjectDependsStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterObjectSchemaStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterOwnerStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterOperatorStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterTypeStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterTableMoveAllStmt:
		case T_AlterTableStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterDomainStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_GrantStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_GrantRoleStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterDefaultPrivilegesStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_DefineStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CompositeTypeStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateEnumStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateRangeStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterEnumStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_ViewStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateFunctionStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterFunctionStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_IndexStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_RuleStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateSeqStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterSeqStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_DoStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_CreatedbStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterDatabaseStmt:
		case T_AlterDatabaseRefreshCollStmt:
		case T_AlterDatabaseSetStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_DropdbStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_NotifyStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_ListenStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_UnlistenStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_LoadStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_CallStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_ClusterStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_VacuumStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_ExplainStmt:
			{
				ExplainStmt *fc_stmt = (ExplainStmt *) fc_parsetree;
				bool		fc_analyze = false;
				ListCell   *fc_lc;

				/* 查看 EXPLAIN ANALYZE 中包含的语句 */
				foreach(fc_lc, fc_stmt->options)
				{
					DefElem    *fc_opt = (DefElem *) lfirst(fc_lc);

					if (strcmp(fc_opt->defname, "analyze") == 0)
						fc_analyze = defGetBoolean(fc_opt);
					/* 不要“中断”，因为 explain.c 将使用最后一个值 */
				}
				if (fc_analyze)
					return GetCommandLogLevel(fc_stmt->query);

				/* 普通 EXPLAIN 并不那么有趣 */
				fc_lev = LOGSTMT_ALL;
			}
			break;

		case T_CreateTableAsStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_RefreshMatViewStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterSystemStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_VariableSetStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_VariableShowStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_DiscardStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_CreateTrigStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateEventTrigStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterEventTrigStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreatePLangStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateDomainStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateRoleStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterRoleStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterRoleSetStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_DropRoleStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_DropOwnedStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_ReassignOwnedStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_LockStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_ConstraintsSetStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_CheckPointStmt:
			fc_lev = LOGSTMT_ALL;
			break;

		case T_ReindexStmt:
			fc_lev = LOGSTMT_ALL;	/* 这应该是 DDL 吗？ */
			break;

		case T_CreateConversionStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateCastStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateOpClassStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateOpFamilyStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateTransformStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterOpFamilyStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreatePolicyStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterPolicyStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterTSDictionaryStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterTSConfigurationStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateAmStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreatePublicationStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterPublicationStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateSubscriptionStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterSubscriptionStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_DropSubscriptionStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_CreateStatsStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterStatsStmt:
			fc_lev = LOGSTMT_DDL;
			break;

		case T_AlterCollationStmt:
			fc_lev = LOGSTMT_DDL;
			break;

			/* 已计划查询 */
		case T_PlannedStmt:
			{
				PlannedStmt *fc_stmt = (PlannedStmt *) fc_parsetree;

				switch (fc_stmt->commandType)
				{
					case CMD_SELECT:
						fc_lev = LOGSTMT_ALL;
						break;

					case CMD_UPDATE:
					case CMD_INSERT:
					case CMD_DELETE:
					case CMD_MERGE:
						fc_lev = LOGSTMT_MOD;
						break;

					case CMD_UTILITY:
						fc_lev = GetCommandLogLevel(fc_stmt->utilityStmt);
						break;

					default:
						elog(WARNING, "unrecognized commandType: %d",
							 (int) fc_stmt->commandType);
						fc_lev = LOGSTMT_ALL;
						break;
				}
			}
			break;

			/* 已解析并重写但未计划的查询 */
		case T_Query:
			{
				Query	   *fc_stmt = (Query *) fc_parsetree;

				switch (fc_stmt->commandType)
				{
					case CMD_SELECT:
						fc_lev = LOGSTMT_ALL;
						break;

					case CMD_UPDATE:
					case CMD_INSERT:
					case CMD_DELETE:
					case CMD_MERGE:
						fc_lev = LOGSTMT_MOD;
						break;

					case CMD_UTILITY:
						fc_lev = GetCommandLogLevel(fc_stmt->utilityStmt);
						break;

					default:
						elog(WARNING, "unrecognized commandType: %d",
							 (int) fc_stmt->commandType);
						fc_lev = LOGSTMT_ALL;
						break;
				}
			}
			break;

		default:
			elog(WARNING, "unrecognized node type: %d",
				 (int) nodeTag(fc_parsetree));
			fc_lev = LOGSTMT_ALL;
			break;
	}

	return fc_lev;
}
